Gemini 與 AI Studio Enter 鍵自訂器

調整 Gemini 與 AI Studio 的 Enter 鍵行為。Gemini:Enter 鍵換行,組合鍵送出。AI Studio:可自訂傳送鍵 (組合鍵、Enter即送、或預設)。附設定面板。

目前為 2025-05-23 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name              Gemini & AI Studio Enter Key Customizer
// @name:en           Gemini & AI Studio Enter Key Customizer
// @name:ja           Gemini & AI Studio Enterキーカスタマイザー
// @name:zh-TW        Gemini 與 AI Studio Enter 鍵自訂器
// @namespace         https://greasyfork.org/en/users/1467948-stonedkhajiit
// @version           1.0.0
// @description       Modifies Enter key behavior in Gemini and AI Studio. Gemini: Enter for newline, Modifier+Enter to send. AI Studio: Configurable send key (modifier, Enter-sends, or native). Includes a settings panel.
// @description:en    Modifies Enter key behavior in Gemini and AI Studio. Gemini: Enter for newline, Modifier+Enter to send. AI Studio: Configurable send key (modifier, Enter-sends, or native). Includes a settings panel.
// @description:ja    GeminiとAI StudioのEnterキー動作を変更。Gemini: Enterで改行、修飾キー+Enterで送信。AI Studio: 送信キーを選択可 (修飾キー、Enter送信、標準)。設定パネルあり。
// @description:zh-TW 調整 Gemini 與 AI Studio 的 Enter 鍵行為。Gemini:Enter 鍵換行,組合鍵送出。AI Studio:可自訂傳送鍵 (組合鍵、Enter即送、或預設)。附設定面板。
// @author            StonedKhajiit
// @match             https://gemini.google.com/*
// @match             https://aistudio.google.com/*
// @grant             GM_setValue
// @grant             GM_getValue
// @grant             GM_registerMenuCommand
// @grant             GM_unregisterMenuCommand
// @license           MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- Constants and Configuration ---
    const SCRIPT_ID = 'GeminiEnterNewlineMultiSite_v1.0.0';

    // CSS Selectors for input fields and send buttons
    const GEMINI_INPUT_SELECTOR_PRIMARY = 'div.ql-editor[contenteditable="true"]';
    const GEMINI_INPUT_SELECTORS_FALLBACK = [
        'textarea[enterkeyhint="send"]', 'textarea[aria-label*="Prompt"]',
        'textarea[placeholder*="Message Gemini"]', 'div[role="textbox"][contenteditable="true"]'
    ];
    const AISTUDIO_INPUT_SELECTORS = [
        'ms-autosize-textarea textarea[aria-label="Type something or tab to choose an example prompt"]',
        'ms-autosize-textarea textarea',
        'ms-autosize-textarea textarea[aria-label="Start typing a prompt"]'
    ];
    const GEMINI_SEND_BUTTON_SELECTORS = [
        'button[aria-label*="Send"]', 'button[aria-label*="傳送"]',
        'button[aria-label*="送信"]', 'button[data-test-id="send-button"]',
    ];
    const AISTUDIO_SEND_BUTTON_SELECTORS = ['button[aria-label="Run"]'];
    const AISTUDIO_SEND_BUTTON_MODIFIER_HINT_SELECTOR = 'span.secondary-key';


    // GM Storage keys and default values for settings
    const GM_GLOBAL_ENABLE_KEY_STORAGE = 'geminiEnterGlobalEnable';
    const GM_MODIFIER_KEY_STORAGE = 'geminiEnterModifierKey';
    const MODIFIER_KEYS = { NONE: 'none', CTRL: 'ctrl', SHIFT: 'shift', ALT: 'alt', NATIVE_GEMINI: 'native_gemini' };
    const DEFAULT_MODIFIER_KEY = MODIFIER_KEYS.CTRL;

    const GM_AISTUDIO_MODE_STORAGE = 'aiStudioKeyMode';
    const AISTUDIO_KEY_MODES = {
        SHIFT_SEND: 'shift_send',
        ALT_SEND: 'alt_send',
        AISTUDIO_SPECIFIC: 'aistudio_specific',
        NATIVE_BEHAVIOR: 'native_behavior'
    };
    const DEFAULT_AISTUDIO_KEY_MODE = AISTUDIO_KEY_MODES.NATIVE_BEHAVIOR;

    // --- State Variables ---
    let activeTextarea = null;
    let isScriptGloballyEnabled = true;
    let currentGlobalModifierKey = DEFAULT_MODIFIER_KEY;
    let currentAIStudioKeyMode = DEFAULT_AISTUDIO_KEY_MODE;
    let menuCommandIds = [];

    // --- Debounce Function ---
    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    // --- Internationalization (i18n) ---
    const i18n = {
        currentLang: 'en',
        strings: {
            'en': {
                notifySettingsSaved: 'Settings saved!',
                notifyScriptEnabled: 'Custom Enter key behavior enabled. Reload page if needed.',
                notifyScriptDisabled: 'Custom Enter key behavior disabled. Reload page if needed.',
                settingsTitle: 'Script Settings',
                geminiKeyModeLabel: 'Gemini Key Mode:',
                aiStudioKeyModeLabel: 'AI Studio Key Mode:',
                closeButton: 'Close',
                saveButton: 'Save',
                openSettingsMenu: 'Configure Enter Key Behavior...',
                enableScriptMenu: 'Enable Custom Enter Key Behavior',
                disableScriptMenu: 'Disable Custom Enter Key Behavior',
                geminiCtrl: 'Ctrl+Enter to send',
                geminiShift: 'Shift+Enter to send',
                geminiAlt: 'Alt+Enter to send',
                geminiNative: 'Use Gemini Native Behavior (Enter sends)',
                aiStudioShift: 'Shift+Enter to send',
                aiStudioAlt: 'Alt+Enter to send',
                aiStudioSpecific: 'Enter to Send, Shift+Enter for Newline',
                aiStudioNative: 'Use AI Studio Native Behavior (Ctrl+Enter sends)',
                modifierCtrl: 'Ctrl',
                modifierShift: 'Shift',
                modifierAlt: 'Alt',
            },
            'zh-TW': {
                notifySettingsSaved: '設定已儲存!',
                notifyScriptEnabled: '自訂 Enter 鍵行為已啟用。若未立即生效請重載頁面。',
                notifyScriptDisabled: '自訂 Enter 鍵行為已停用。若未立即生效請重載頁面。',
                settingsTitle: '腳本設定',
                geminiKeyModeLabel: 'Gemini 按鍵模式:',
                aiStudioKeyModeLabel: 'AI Studio 按鍵模式:',
                closeButton: '關閉',
                saveButton: '儲存',
                openSettingsMenu: '設定 Enter 鍵行為...',
                enableScriptMenu: '啟用自訂 Enter 鍵行為',
                disableScriptMenu: '停用自訂 Enter 鍵行為',
                geminiCtrl: 'Ctrl+Enter 送出',
                geminiShift: 'Shift+Enter 送出',
                geminiAlt: 'Alt+Enter 送出',
                geminiNative: '使用 Gemini 原生行為 (Enter 送出)',
                aiStudioShift: 'Shift+Enter 送出',
                aiStudioAlt: 'Alt+Enter 送出',
                aiStudioSpecific: 'Enter 送出,Shift+Enter 換行',
                aiStudioNative: '使用 AI Studio 原生行為 (Ctrl+Enter 送出)',
                modifierCtrl: 'Ctrl',
                modifierShift: 'Shift',
                modifierAlt: 'Alt',
            },
            'ja': {
                notifySettingsSaved: '設定を保存しました!',
                notifyScriptEnabled: 'Enterキーのカスタム動作が有効になりました。必要に応じてページを再読み込みしてください。',
                notifyScriptDisabled: 'Enterキーのカスタム動作が無効になりました。必要に応じてページを再読み込みしてください。',
                settingsTitle: 'スクリプト設定',
                geminiKeyModeLabel: 'Gemini のキーモード:',
                aiStudioKeyModeLabel: 'AI Studio のキーモード:',
                closeButton: '閉じる',
                saveButton: '保存',
                openSettingsMenu: 'Enterキーの動作を設定...',
                enableScriptMenu: 'Enterキーのカスタム動作を有効化',
                disableScriptMenu: 'Enterキーのカスタム動作を無効化',
                geminiCtrl: 'Ctrl+Enter で送信',
                geminiShift: 'Shift+Enter で送信',
                geminiAlt: 'Alt+Enter で送信',
                geminiNative: 'Gemini ネイティブ動作を使用 (Enter で送信)',
                aiStudioShift: 'Shift+Enter で送信',
                aiStudioAlt: 'Alt+Enter で送信',
                aiStudioSpecific: 'Enter で送信、Shift+Enter で改行',
                aiStudioNative: 'AI Studio ネイティブ動作を使用 (Ctrl+Enter で送信)',
                modifierCtrl: 'Ctrl',
                modifierShift: 'Shift',
                modifierAlt: 'Alt',
            }
        },
        detectLanguage() {
            const lang = navigator.language || navigator.userLanguage;
            if (lang) {
                if (lang.startsWith('ja')) this.currentLang = 'ja';
                else if (lang.startsWith('zh-TW') || lang.startsWith('zh-Hant')) this.currentLang = 'zh-TW';
                else if (lang.startsWith('en')) this.currentLang = 'en';
                else this.currentLang = 'en';
            } else {
                this.currentLang = 'en';
            }
        },
        get(key, ...args) {
            const langStrings = this.strings[this.currentLang] || this.strings.en;
            const template = langStrings[key] || (this.strings.en && this.strings.en[key]);
            if (typeof template === 'function') return template(...args);
            if (typeof template === 'string') return template;
            console.warn(`[${SCRIPT_ID}] Missing i18n string for key: ${key} in lang: ${this.currentLang}`);
            return `Missing string: ${key}`;
        }
    };

    // --- UI Functions ---
    function createSettingsUI() {
        if (document.getElementById('gemini-ai-settings-overlay')) return;

        const overlay = document.createElement('div');
        overlay.id = 'gemini-ai-settings-overlay';
        overlay.classList.add('hidden');

        const style = document.createElement('style');
        style.textContent = `
            @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
            #gemini-ai-settings-overlay {
                position: fixed; inset: 0px; background-color: rgba(0, 0, 0, 0.6);
                display: flex; align-items: center; justify-content: center;
                z-index: 2147483647; font-family: 'Inter', Arial, sans-serif;
                opacity: 0; transition: opacity 0.2s ease-in-out;
            }
            #gemini-ai-settings-overlay.visible { opacity: 1; }
            #gemini-ai-settings-overlay.hidden { display: none !important; }
            #gemini-ai-settings-panel {
                background-color: #ffffff; color: #1f2937;
                padding: 18px; border-radius: 8px;
                box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04);
                width: 90%; max-width: 420px;
                position: relative; overflow-y: auto; max-height: 90vh;
            }
            body.userscript-dark-mode #gemini-ai-settings-panel { background-color: #2d3748; color: #e2e8f0; }
            body.userscript-dark-mode #gemini-ai-settings-panel h2,
            body.userscript-dark-mode #gemini-ai-settings-panel h3,
            body.userscript-dark-mode #gemini-ai-settings-panel label { color: #e2e8f0; }
            body.userscript-dark-mode #gemini-ai-settings-panel button#gemini-ai-close-btn { background-color: #4a5568; color: #e2e8f0; }
            body.userscript-dark-mode #gemini-ai-settings-panel button#gemini-ai-close-btn:hover { background-color: #718096; }
            body.userscript-dark-mode #gemini-ai-settings-panel input[type="radio"] { filter: invert(1) hue-rotate(180deg); }

            #gemini-ai-settings-panel h2 { font-size: 1.15rem; font-weight: 600; margin-bottom: 0.8rem; }
            #gemini-ai-settings-panel h3 { font-size: 1rem; font-weight: 500; margin-bottom: 0.5rem; margin-top: 0.7rem; }
            #gemini-ai-settings-panel .section-divider { border-top: 1px solid #e5e7eb; margin-top: 1rem; margin-bottom: 1rem; }
            body.userscript-dark-mode #gemini-ai-settings-panel .section-divider { border-top-color: #4a5568; }

            #gemini-ai-settings-panel .options-group > div { margin-bottom: 0.3rem; }
            #gemini-ai-settings-panel label { display: inline-flex; align-items: center; cursor: pointer; font-size: 0.875rem; }
            #gemini-ai-settings-panel input[type="radio"] { margin-right: 0.4rem; cursor: pointer; transform: scale(0.95); }

            .settings-buttons-container { display: flex; justify-content: flex-end; margin-top: 1rem; gap: 0.5rem; }
            #gemini-ai-settings-panel button {
                padding: 0.4rem 0.9rem; border-radius: 6px;
                font-weight: 500; transition: background-color 0.2s ease, box-shadow 0.2s ease;
                border: none; cursor: pointer; font-size: 0.875rem;
            }
            #gemini-ai-settings-panel button#gemini-ai-close-btn { background-color: #e5e7eb; color: #374151; }
            #gemini-ai-settings-panel button#gemini-ai-close-btn:hover { background-color: #d1d5db; }
            #gemini-ai-settings-panel button#gemini-ai-save-btn { background-color: #3b82f6; color: white; }
            #gemini-ai-settings-panel button#gemini-ai-save-btn:hover { background-color: #2563eb; }

            #gemini-ai-notification {
                position: fixed; bottom: 25px; left: 50%;
                transform: translateX(-50%);
                background-color: #10b981; color: white;
                padding: 0.8rem 1.5rem; border-radius: 6px;
                box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06);
                z-index: 2147483647; opacity: 0;
                transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
                font-family: 'Inter', Arial, sans-serif; font-size: 0.9rem;
            }
            #gemini-ai-notification.visible { opacity: 1; transform: translateX(-50%) translateY(0px); }
            #gemini-ai-notification.hidden { display: none !important; }
        `;
        document.head.appendChild(style);

        const settingsPanel = document.createElement('div');
        settingsPanel.id = 'gemini-ai-settings-panel';

        const title = document.createElement('h2');
        title.textContent = i18n.get('settingsTitle');
        settingsPanel.appendChild(title);

        const geminiTitle = document.createElement('h3');
        geminiTitle.textContent = i18n.get('geminiKeyModeLabel');
        settingsPanel.appendChild(geminiTitle);
        const geminiOptionsDiv = document.createElement('div');
        geminiOptionsDiv.id = 'gemini-key-options';
        geminiOptionsDiv.className = 'options-group';
        settingsPanel.appendChild(geminiOptionsDiv);

        settingsPanel.appendChild(document.createElement('div')).className = 'section-divider';

        const aistudioTitle = document.createElement('h3');
        aistudioTitle.textContent = i18n.get('aiStudioKeyModeLabel');
        settingsPanel.appendChild(aistudioTitle);
        const aistudioOptionsDiv = document.createElement('div');
        aistudioOptionsDiv.id = 'aistudio-key-options';
        aistudioOptionsDiv.className = 'options-group';
        settingsPanel.appendChild(aistudioOptionsDiv);

        const buttonDiv = document.createElement('div');
        buttonDiv.className = 'settings-buttons-container';
        const closeButton = document.createElement('button');
        closeButton.id = 'gemini-ai-close-btn';
        closeButton.textContent = i18n.get('closeButton');
        buttonDiv.appendChild(closeButton);
        const saveButton = document.createElement('button');
        saveButton.id = 'gemini-ai-save-btn';
        saveButton.textContent = i18n.get('saveButton');
        buttonDiv.appendChild(saveButton);
        settingsPanel.appendChild(buttonDiv);

        overlay.appendChild(settingsPanel);
        document.body.appendChild(overlay);

        const notificationDiv = document.createElement('div');
        notificationDiv.id = 'gemini-ai-notification';
        notificationDiv.classList.add('hidden');
        document.body.appendChild(notificationDiv);

        closeButton.addEventListener('click', closeSettings);
        saveButton.addEventListener('click', saveSettingsFromUI);
        overlay.addEventListener('click', (e) => { if (e.target === overlay) closeSettings(); });

        if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
            document.body.classList.add('userscript-dark-mode');
        }
        window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
            document.body.classList.toggle('userscript-dark-mode', e.matches);
        });
    }

    function populateSettingsUI() {
        const geminiOptionsDiv = document.getElementById('gemini-key-options');
        const aistudioOptionsDiv = document.getElementById('aistudio-key-options');

        if (!geminiOptionsDiv || !aistudioOptionsDiv) return;

        while (geminiOptionsDiv.firstChild) geminiOptionsDiv.removeChild(geminiOptionsDiv.firstChild);
        while (aistudioOptionsDiv.firstChild) aistudioOptionsDiv.removeChild(aistudioOptionsDiv.firstChild);

        const geminiModifierOptions = [
            { key: MODIFIER_KEYS.CTRL, labelKey: 'geminiCtrl' },
            { key: MODIFIER_KEYS.SHIFT, labelKey: 'geminiShift' },
            { key: MODIFIER_KEYS.ALT, labelKey: 'geminiAlt' },
            { key: MODIFIER_KEYS.NATIVE_GEMINI, labelKey: 'geminiNative' },
        ];
        geminiModifierOptions.forEach(opt => {
            const div = document.createElement('div');
            const input = document.createElement('input');
            input.type = 'radio'; input.name = 'geminiKeyMode'; input.id = `gemini-${opt.key}`; input.value = opt.key;
            if (currentGlobalModifierKey === opt.key) input.checked = true;
            const label = document.createElement('label');
            label.htmlFor = `gemini-${opt.key}`; label.textContent = i18n.get(opt.labelKey);
            div.appendChild(input); div.appendChild(label);
            geminiOptionsDiv.appendChild(div);
        });

        const aiStudioModeOptions = [
            { mode: AISTUDIO_KEY_MODES.SHIFT_SEND, labelKey: 'aiStudioShift' },
            { mode: AISTUDIO_KEY_MODES.ALT_SEND, labelKey: 'aiStudioAlt' },
            { mode: AISTUDIO_KEY_MODES.AISTUDIO_SPECIFIC, labelKey: 'aiStudioSpecific' },
            { mode: AISTUDIO_KEY_MODES.NATIVE_BEHAVIOR, labelKey: 'aiStudioNative' },
        ];
        aiStudioModeOptions.forEach(opt => {
            const div = document.createElement('div');
            const input = document.createElement('input');
            input.type = 'radio'; input.name = 'aistudioKeyMode'; input.id = `aistudio-${opt.mode}`; input.value = opt.mode;
            if (currentAIStudioKeyMode === opt.mode) input.checked = true;
            const label = document.createElement('label');
            label.htmlFor = `aistudio-${opt.mode}`; label.textContent = i18n.get(opt.labelKey);
            div.appendChild(input); div.appendChild(label);
            aistudioOptionsDiv.appendChild(div);
        });
    }

    function openSettings() {
        loadSettings();
        populateSettingsUI();
        const overlay = document.getElementById('gemini-ai-settings-overlay');
        if (overlay) {
            overlay.classList.remove('hidden');
            void overlay.offsetWidth;
            overlay.classList.add('visible');
        }
    }

    function closeSettings() {
        const overlay = document.getElementById('gemini-ai-settings-overlay');
        if (overlay) {
            overlay.classList.remove('visible');
            setTimeout(() => {
                if (!overlay.classList.contains('visible')) {
                     overlay.classList.add('hidden');
                }
            }, 200);
        }
    }

    function showNotification(message) {
        const notificationDiv = document.getElementById('gemini-ai-notification');
        if (notificationDiv) {
            notificationDiv.textContent = message;
            notificationDiv.classList.remove('hidden');
            void notificationDiv.offsetWidth;
            notificationDiv.classList.add('visible');

            setTimeout(() => {
                notificationDiv.classList.remove('visible');
                setTimeout(() => {
                    if (!notificationDiv.classList.contains('visible')) {
                        notificationDiv.classList.add('hidden');
                    }
                }, 300);
            }, 2500);
        }
    }

    // --- Core Logic Functions ---
    function loadSettings() {
        isScriptGloballyEnabled = GM_getValue(GM_GLOBAL_ENABLE_KEY_STORAGE, true);

        const savedGeminiModifier = GM_getValue(GM_MODIFIER_KEY_STORAGE, DEFAULT_MODIFIER_KEY);
        currentGlobalModifierKey = Object.values(MODIFIER_KEYS).includes(savedGeminiModifier) ? savedGeminiModifier : DEFAULT_MODIFIER_KEY;

        const savedAIStudioMode = GM_getValue(GM_AISTUDIO_MODE_STORAGE, DEFAULT_AISTUDIO_KEY_MODE);
        currentAIStudioKeyMode = Object.values(AISTUDIO_KEY_MODES).includes(savedAIStudioMode) ? savedAIStudioMode : DEFAULT_AISTUDIO_KEY_MODE;
    }

    function saveSettingsFromUI() {
        const selectedGeminiMode = document.querySelector('input[name="geminiKeyMode"]:checked')?.value;
        if (selectedGeminiMode) {
            saveGeminiKeyModeSetting(selectedGeminiMode);
        } else {
             saveGeminiKeyModeSetting(DEFAULT_MODIFIER_KEY);
        }

        const selectedAIStudioMode = document.querySelector('input[name="aistudioKeyMode"]:checked')?.value;
        if (selectedAIStudioMode) {
            saveAIStudioKeyModeSetting(selectedAIStudioMode);
        } else {
            saveAIStudioKeyModeSetting(DEFAULT_AISTUDIO_KEY_MODE);
        }

        registerMenuCommand();
        showNotification(i18n.get('notifySettingsSaved'));
        closeSettings();
    }

    function saveGeminiKeyModeSetting(key) {
        if (Object.values(MODIFIER_KEYS).includes(key) && key !== MODIFIER_KEYS.NONE) {
            currentGlobalModifierKey = key;
            GM_setValue(GM_MODIFIER_KEY_STORAGE, key);
            updateActiveTextareaListener(); // Important to update listener status
        }
    }

    function saveAIStudioKeyModeSetting(mode) {
        if (Object.values(AISTUDIO_KEY_MODES).includes(mode)) {
            currentAIStudioKeyMode = mode;
            GM_setValue(GM_AISTUDIO_MODE_STORAGE, mode);
            updateAIStudioButtonModifierHint();
            updateActiveTextareaListener();
        }
    }

    function updateAIStudioButtonModifierHint() {
        if (!window.location.hostname.includes('aistudio.google.com')) {
            return;
        }

        const sendButton = findSendButton();
        if (sendButton) {
            const hintSpan = sendButton.querySelector(AISTUDIO_SEND_BUTTON_MODIFIER_HINT_SELECTOR);
            if (hintSpan) {
                hintSpan.style.display = 'inline';
                let hintText = i18n.get('modifierCtrl');

                switch (currentAIStudioKeyMode) {
                    case AISTUDIO_KEY_MODES.SHIFT_SEND:
                        hintText = i18n.get('modifierShift');
                        break;
                    case AISTUDIO_KEY_MODES.ALT_SEND:
                        hintText = i18n.get('modifierAlt');
                        break;
                    case AISTUDIO_KEY_MODES.AISTUDIO_SPECIFIC:
                        hintSpan.style.display = 'none';
                        hintText = '';
                        break;
                    case AISTUDIO_KEY_MODES.NATIVE_BEHAVIOR:
                        hintText = i18n.get('modifierCtrl');
                        break;
                }
                hintSpan.textContent = hintSpan.style.display !== 'none' ? hintText + ' ' : '';
            }
        }
    }


    function handleKeydown(event) {
        if (window.location.hostname.includes('gemini.google.com') && !isScriptGloballyEnabled) {
            return;
        }
        if (event.target !== activeTextarea && (!activeTextarea || !activeTextarea.contains(event.target))) {
            return;
        }

        const currentHost = window.location.hostname;
        const ctrlOnly = event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey;
        const shiftOnly = event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey;
        const altOnly = event.altKey && !event.ctrlKey && !event.shiftKey && !event.metaKey;
        const plainEnter = !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey;

        if (event.key === 'Enter') {
            if (currentHost.includes('gemini.google.com')) {
                if (currentGlobalModifierKey === MODIFIER_KEYS.NATIVE_GEMINI) {
                    return;
                }
                applyModifierSendBehavior(event, currentGlobalModifierKey, ctrlOnly, shiftOnly, altOnly, plainEnter, activeTextarea);
            } else if (currentHost.includes('aistudio.google.com')) {
                if (currentAIStudioKeyMode === AISTUDIO_KEY_MODES.NATIVE_BEHAVIOR) {
                    return;
                } else if (currentAIStudioKeyMode === AISTUDIO_KEY_MODES.AISTUDIO_SPECIFIC) {
                    if (plainEnter) {
                        event.preventDefault(); event.stopImmediatePropagation();
                        const sendButton = findSendButton();
                        if (sendButton && !sendButton.disabled) sendButton.click();
                        else { const form = event.target?.closest('form'); if (form) form.requestSubmit ? form.requestSubmit() : form.submit(); }
                    } else if (shiftOnly) {
                        event.preventDefault(); event.stopImmediatePropagation();
                        insertNewline(activeTextarea);
                    } else {
                        event.preventDefault(); event.stopImmediatePropagation();
                    }
                } else if (currentAIStudioKeyMode === AISTUDIO_KEY_MODES.SHIFT_SEND) {
                    applyModifierSendBehavior(event, MODIFIER_KEYS.SHIFT, ctrlOnly, shiftOnly, altOnly, plainEnter, activeTextarea);
                } else if (currentAIStudioKeyMode === AISTUDIO_KEY_MODES.ALT_SEND) {
                    applyModifierSendBehavior(event, MODIFIER_KEYS.ALT, ctrlOnly, shiftOnly, altOnly, plainEnter, activeTextarea);
                }
            }
        }
    }

    function insertNewline(element) {
        if (!element) return;
        if (element.isContentEditable) {
            element.focus();
            let success = false;
            try { success = document.execCommand('insertParagraph', false, null); } catch (e) { /* console.warn("execCommand('insertParagraph') failed:", e); */ }
            if (!success) {
                try {
                    const selection = window.getSelection();
                    if (selection && selection.rangeCount > 0) {
                        const range = selection.getRangeAt(0);
                        const br = document.createElement('br');
                        range.deleteContents();
                        range.insertNode(br);
                        const newRange = document.createRange();
                        newRange.setStartAfter(br);
                        newRange.collapse(true);
                        selection.removeAllRanges();
                        selection.addRange(newRange);
                    } else {
                       document.execCommand('insertHTML', false, '<br>');
                    }
                } catch (e) { /* console.warn("Fallback newline insertion for contentEditable failed:", e); */ }
            }
        } else if (element.tagName === 'TEXTAREA') {
            const start = element.selectionStart; const end = element.selectionEnd;
            element.value = `${element.value.substring(0, start)}\n${element.value.substring(end)}`;
            element.selectionStart = element.selectionEnd = start + 1;
            element.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
        }
    }


    function applyModifierSendBehavior(event, modifierKeyToSend, ctrlOnly, shiftOnly, altOnly, plainEnter, activeTextareaElement) {
        let send = false;
        if ((modifierKeyToSend === MODIFIER_KEYS.CTRL && ctrlOnly) ||
            (modifierKeyToSend === MODIFIER_KEYS.SHIFT && shiftOnly) ||
            (modifierKeyToSend === MODIFIER_KEYS.ALT && altOnly)) {
            send = true;
        }

        if (send) {
            event.preventDefault(); event.stopImmediatePropagation();
            const sendButton = findSendButton();
            if (sendButton && !sendButton.disabled) sendButton.click();
            else {
                const form = event.target?.closest('form');
                if (form) form.requestSubmit ? form.requestSubmit() : form.submit();
            }
        } else if (plainEnter) {
            event.preventDefault(); event.stopImmediatePropagation();
            insertNewline(activeTextareaElement);
        } else {
            const isShiftEnterForNativeNewline = shiftOnly && modifierKeyToSend !== MODIFIER_KEYS.SHIFT;
            if (!isShiftEnterForNativeNewline) {
                 event.preventDefault(); event.stopImmediatePropagation();
            }
        }
    }

    function updateActiveTextareaListener() {
        if (activeTextarea) {
            const listenerAttached = activeTextarea.dataset.keydownListenerAttached === 'true';
            const onGemini = window.location.hostname.includes('gemini.google.com');
            const onAIStudio = window.location.hostname.includes('aistudio.google.com');

            let shouldCurrentlyBeAttached = false;
            if (onGemini) {
                shouldCurrentlyBeAttached = isScriptGloballyEnabled && currentGlobalModifierKey !== MODIFIER_KEYS.NATIVE_GEMINI;
            } else if (onAIStudio) {
                shouldCurrentlyBeAttached = currentAIStudioKeyMode !== AISTUDIO_KEY_MODES.NATIVE_BEHAVIOR;
            }

            if (listenerAttached && !shouldCurrentlyBeAttached) {
                activeTextarea.removeEventListener('keydown', handleKeydown, true);
                delete activeTextarea.dataset.keydownListenerAttached;
                // console.log(`[${SCRIPT_ID}] Keydown listener removed from:`, activeTextarea);
            } else if (!listenerAttached && shouldCurrentlyBeAttached) {
                activeTextarea.addEventListener('keydown', handleKeydown, true);
                activeTextarea.dataset.keydownListenerAttached = 'true';
                // console.log(`[${SCRIPT_ID}] Keydown listener attached to:`, activeTextarea);
            }
        }
    }

    function toggleScriptGlobally() {
        isScriptGloballyEnabled = !isScriptGloballyEnabled;
        GM_setValue(GM_GLOBAL_ENABLE_KEY_STORAGE, isScriptGloballyEnabled);
        updateActiveTextareaListener();
        registerMenuCommand();
        showNotification(isScriptGloballyEnabled ? i18n.get('notifyScriptEnabled') : i18n.get('notifyScriptDisabled'));
    }

    function registerMenuCommand() {
        menuCommandIds.forEach(id => { if (typeof GM_unregisterMenuCommand === 'function') try { GM_unregisterMenuCommand(id); } catch (e) { /* console.warn("Error unregistering menu command:", e); */ } });
        menuCommandIds = [];

        try {
            const settingsCmdId = GM_registerMenuCommand(i18n.get('openSettingsMenu'), openSettings, 's');
            if (settingsCmdId) menuCommandIds.push(settingsCmdId);
        } catch (e) { console.error(`[${SCRIPT_ID}] Error registering 'Open Settings' menu command:`, e); }

        try {
            const toggleCmdText = isScriptGloballyEnabled ? i18n.get('disableScriptMenu') : i18n.get('enableScriptMenu');
            const toggleCmdId = GM_registerMenuCommand(toggleCmdText, toggleScriptGlobally, 't');
            if (toggleCmdId) menuCommandIds.push(toggleCmdId);
        } catch (e) { console.error(`[${SCRIPT_ID}] Error registering toggle script menu command:`, e); }
    }

    // --- Initialization and Observation ---
    function findTextarea() {
        let el;
        const currentHost = window.location.hostname;
        if (currentHost.includes('aistudio.google.com')) {
            for (const selector of AISTUDIO_INPUT_SELECTORS) {
                el = document.querySelector(selector); if (el) return el;
            }
        } else if (currentHost.includes('gemini.google.com')) {
            el = document.querySelector(GEMINI_INPUT_SELECTOR_PRIMARY); if (el) return el;
            for (const selector of GEMINI_INPUT_SELECTORS_FALLBACK) {
                el = document.querySelector(selector); if (el) return el;
            }
        }
        return null;
    }

    function findSendButton() {
        let el;
        const currentHost = window.location.hostname;
        if (currentHost.includes('aistudio.google.com')) {
            for (const selector of AISTUDIO_SEND_BUTTON_SELECTORS) {
                el = document.querySelector(selector); if (el) return el;
            }
        } else if (currentHost.includes('gemini.google.com')) {
            for (const selector of GEMINI_SEND_BUTTON_SELECTORS) {
                el = document.querySelector(selector); if (el) return el;
            }
        }
        return null;
    }

    // Debounced handler for DOM changes
    const debouncedDOMChangeHandler = debounce(() => {
        const newTextarea = findTextarea();
        if (newTextarea) {
            const onGemini = window.location.hostname.includes('gemini.google.com');
            const onAIStudio = window.location.hostname.includes('aistudio.google.com');
            const needsListenerOnGemini = onGemini && isScriptGloballyEnabled && currentGlobalModifierKey !== MODIFIER_KEYS.NATIVE_GEMINI;
            const needsListenerOnAIStudio = onAIStudio && currentAIStudioKeyMode !== AISTUDIO_KEY_MODES.NATIVE_BEHAVIOR;
            const listenerShouldBeAttached = needsListenerOnGemini || needsListenerOnAIStudio;

            if (activeTextarea !== newTextarea || (activeTextarea && activeTextarea.dataset.keydownListenerAttached !== 'true' && listenerShouldBeAttached)) {
                if (activeTextarea && activeTextarea.dataset.keydownListenerAttached === 'true') {
                    activeTextarea.removeEventListener('keydown', handleKeydown, true);
                    delete activeTextarea.dataset.keydownListenerAttached;
                }
                activeTextarea = newTextarea;
                updateActiveTextareaListener(); // This will attach/detach based on current state
            }
        } else if (activeTextarea && activeTextarea.dataset.keydownListenerAttached === 'true') {
            activeTextarea.removeEventListener('keydown', handleKeydown, true);
            delete activeTextarea.dataset.keydownListenerAttached;
            activeTextarea = null;
        }

        if (window.location.hostname.includes('aistudio.google.com')) {
            updateAIStudioButtonModifierHint();
        }
    }, 300); // 300ms debounce time

    const observeDOM = function() {
        const observer = new MutationObserver((mutationsList, obs) => {
            debouncedDOMChangeHandler();
        });
        observer.observe(document.body, { childList: true, subtree: true });
    };

    function init() {
        i18n.detectLanguage();
        loadSettings();
        createSettingsUI();
        registerMenuCommand();
        observeDOM();

        // Initial setup for textarea and button hint
        activeTextarea = findTextarea();
        updateActiveTextareaListener();

        // Delay initial AI Studio button hint update slightly to allow page to settle
        if (window.location.hostname.includes('aistudio.google.com')) {
            setTimeout(updateAIStudioButtonModifierHint, 500); // Increased delay
        } else {
            updateAIStudioButtonModifierHint(); // For Gemini or other contexts, update immediately (though it does nothing)
        }

        console.log(`[${SCRIPT_ID}] Initialized. Gemini Mode: ${currentGlobalModifierKey}, AI Studio Mode: ${currentAIStudioKeyMode}, Script Enabled: ${isScriptGloballyEnabled}`);
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();