AI Studio - Advanced Control Suite (History, UI, Lag Fix)

Advanced control for Google AI Studio: Chat history modes (Exchanges, Vibe), UI Hiding (Sidebars, System Instr.), Input Lag Fix, Dark Theme Popup.

目前為 2025-04-02 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         AI Studio - Advanced Control Suite (History, UI, Lag Fix)
// @namespace    http://tampermonkey.net/
// @version      4.0
// @description  Advanced control for Google AI Studio: Chat history modes (Exchanges, Vibe), UI Hiding (Sidebars, System Instr.), Input Lag Fix, Dark Theme Popup.
// @author       so it goes...again & Gemini
// @match        https://aistudio.google.com/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @run-at       document-idle
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- Configuration ---

    // --- !!! CRITICAL SELECTORS - VERIFY THESE CAREFULLY !!! ---
    const LEFT_SIDEBAR_SELECTOR = 'ms-navbar';// Confirmed from previous snippet
    const RIGHT_SIDEBAR_SELECTOR = 'ms-run-settings';/* !!! VERIFY THIS SELECTOR !!! */ // Verify when OPEN
    const SYSTEM_INSTRUCTIONS_SELECTOR = 'ms-system-instructions';// Assumed Correct - Verify if possible
    const CHAT_INPUT_SELECTOR = 'textarea[aria-label="Type something"]'; // <<< CONFIRMED from snippet
    const RUN_BUTTON_SELECTOR = 'button.run-button[aria-label="Run"]';// <<< CONFIRMED from snippet
    const OVERALL_LAYOUT_SELECTOR = 'body > app-root > ms-app > div';// <<< Best guess, update if needed
    const CHAT_CONTAINER_SELECTOR = 'ms-autoscroll-container';// Stable
    const USER_TURN_SELECTOR = 'ms-chat-turn:has([data-turn-role="User"])'; // Stable
    const AI_TURN_SELECTOR = 'ms-chat-turn:has([data-turn-role="Model"])'; // Stable
    const BUTTON_CONTAINER_SELECTOR = 'div.right-side';// Stable
    // --- END CRITICAL SELECTORS ---

    const SCRIPT_BUTTON_ID = 'advanced-control-toggle-button';
    const POPUP_ID = 'advanced-control-popup';
    const FAKE_INPUT_ID = 'advanced-control-fake-input';
    const FAKE_RUN_BUTTON_ID = 'advanced-control-fake-run-button';
    const LAYOUT_HIDE_CLASS = 'adv-controls-hide-ui'; // Class added to OVERALL_LAYOUT_SELECTOR

    // Settings Keys
    const SETTINGS_KEY = 'aiStudioAdvancedControlSettings_v4'; // New key for this version

    // Default Settings
    const DEFAULT_SETTINGS = {
        mode: 'manual',// 'off' | 'manual' | 'auto' | 'vibe'
        numTurnsToShow: 2,// Number of exchanges (Manual/Auto) or AI turns (unused in Vibe v4)
        hideSidebars: false,// User preference for hiding sidebars
        hideSystemInstructions: false, // User preference for hiding sys instructions
        useLagFixInput: false,// User preference for the input lag fix
    };

    // --- State ---
    let settings = { ...DEFAULT_SETTINGS };
    let isCurrentlyHidden = false; // Chat history hidden state
    let scriptToggleButton = null;
    let popupElement = null;
    let chatObserver = null;
    let debounceTimer = null;
    let realChatInput = null; // Cache the real input element for lag fix
    let realRunButton = null; // Cache the real run button for lag fix
    let fakeChatInput = null; // Cache the fake input element

    // --- Icons ---
    const ICON_VISIBLE = 'visibility';
    const ICON_HIDDEN = 'visibility_off';
    const ICON_VIBE = 'neurology'; // Or choose another icon for Vibe button

        // --- Core Logic: Chat History Hiding ---
    function applyChatVisibilityRules() {
        console.log("AC Script: Applying chat visibility. Mode:", settings.mode, "Num:", settings.numTurnsToShow);
        const chatContainer = document.querySelector(CHAT_CONTAINER_SELECTOR);
        if (!chatContainer) {
             console.warn("AC Script: Chat container not found for visibility rules.");
             return;
        }

        const allUserTurns = Array.from(chatContainer.querySelectorAll(USER_TURN_SELECTOR));
        const allAiTurns = Array.from(chatContainer.querySelectorAll(AI_TURN_SELECTOR));
        // Query all turns together for simplicity in show/hide all scenarios and iteration
        const allTurns = Array.from(chatContainer.querySelectorAll(`${USER_TURN_SELECTOR}, ${AI_TURN_SELECTOR}`));

        let turnsToShow = [];
        let localDidHideSomething = false;

        // Helper to set display style idempotently
        const setDisplay = (element, visible) => {
            const targetDisplay = visible ? '' : 'none';
            if (element.style.display !== targetDisplay) {
                element.style.display = targetDisplay;
            }
        };

        switch (settings.mode) {
            case 'off':
                // --- Show All ---
                allTurns.forEach(turn => setDisplay(turn, true));
                localDidHideSomething = false;
                break; // End of 'off' case

            case 'vibe':
                // --- VIBE Mode: Show only the very last AI turn, hide all user turns ---
                allUserTurns.forEach(turn => {
                    setDisplay(turn, false);
                });
                 // If any user turns exist, we definitely hid something (or tried to)
                 if (allUserTurns.length > 0) localDidHideSomething = true;

                // Now handle AI turns
                if (allAiTurns.length > 0) {
                    const lastAiTurn = allAiTurns[allAiTurns.length - 1];
                    allAiTurns.forEach(turn => {
                        const shouldBeVisible = (turn === lastAiTurn);
                        setDisplay(turn, shouldBeVisible);
                        // If we hide any AI turn (i.e., not the last one), mark as hidden
                        if (!shouldBeVisible) localDidHideSomething = true;
                    });
                }
                // No 'else' needed - if no AI turns, nothing to show.

                break; // End of 'vibe' case

            case 'manual':
            case 'auto':{
                // --- Manual/Auto Mode: Show last N *exchanges* (User+AI pairs) ---
                const numExchangesToShow = settings.numTurnsToShow;

                if (numExchangesToShow <= 0) { // Show all if 0 or less
                    allTurns.forEach(turn => setDisplay(turn, true));
                    localDidHideSomething = false;
                } else {
                    let exchangesFound = 0;
                    turnsToShow = []; // Stores the elements that should be visible
                    // Iterate backwards through all turns to find pairs/exchanges
                    for (let i = allTurns.length - 1; i >= 0; i--) {
                        const currentTurn = allTurns[i];

                        if (currentTurn.matches(AI_TURN_SELECTOR)) {
                            // Found an AI turn
                            exchangesFound++; // Count this as (part of) an exchange
                            turnsToShow.unshift(currentTurn); // Definitely show the AI turn

                            // Look for the User turn immediately before it
                            if (i > 0 && allTurns[i - 1].matches(USER_TURN_SELECTOR)) {
                                turnsToShow.unshift(allTurns[i - 1]); // Show the preceding User turn
                                i--; // Decrement i again to skip this User turn in the next iteration
                            }
                            // If no preceding user turn, it's an "orphan" AI start, still counts as 1 exchange

                        } else if (currentTurn.matches(USER_TURN_SELECTOR)) {
                            // Found a User turn without a following AI (maybe the very last prompt)
                             exchangesFound++; // Count this incomplete exchange
                             turnsToShow.unshift(currentTurn); // Show this User turn
                        }

                        // Stop if we have found enough exchanges
                        if (exchangesFound >= numExchangesToShow) {
                            break;
                        }
                    } // End backwards loop

                    // Now apply visibility based on the collected turnsToShow list
                    allTurns.forEach(turn => {
                        const shouldBeVisible = turnsToShow.includes(turn);
                        setDisplay(turn, shouldBeVisible);
                        if (!shouldBeVisible) localDidHideSomething = true; // If any turn is hidden
                    });
                }
                break; // End of 'manual'/'auto' case
            }
        } // End switch

        // --- Update button icon state ---
        if (isCurrentlyHidden !== localDidHideSomething) {
            isCurrentlyHidden = localDidHideSomething;
            updateScriptToggleButtonAppearance(); // Assumes this function exists elsewhere
            console.log(`AC Script: Chat visibility updated. Currently hidden: ${isCurrentlyHidden}`);
        }
    } // End applyChatVisibilityRules
    // --- Core Logic: UI Element Hiding ---
    function applyLayoutRules() {
        const layoutContainer = document.querySelector(OVERALL_LAYOUT_SELECTOR);
        if (!layoutContainer) {
            console.warn("AC Script: Overall layout container not found:", OVERALL_LAYOUT_SELECTOR);
            return;
        }

        const forceHide = settings.mode === 'vibe'; // Vibe mode forces UI hidden
        const shouldHideSidebars = forceHide || settings.hideSidebars;
        const shouldHideSysInstructions = forceHide || settings.hideSystemInstructions;
        const shouldApplyLagFix = forceHide || settings.useLagFixInput;

        // Toggle main class on layout container
        layoutContainer.classList.toggle(`${LAYOUT_HIDE_CLASS}-sidebars`, shouldHideSidebars);
        layoutContainer.classList.toggle(`${LAYOUT_HIDE_CLASS}-sysinstruct`, shouldHideSysInstructions);

        // Activate/Deactivate Lag Fix Input
        toggleLagFixInput(shouldApplyLagFix);

        console.log(`AC Script: Applied Layout Rules. Mode: ${settings.mode}, Hide Sidebars: ${shouldHideSidebars}, Hide SysInstruct: ${shouldHideSysInstructions}, LagFix: ${shouldApplyLagFix}`);

        // Update UI state in popup if open
        if (popupElement?.style.display === 'block') {
            updatePopupUIState();
        }
    }


    // --- Settings Management ---
    async function loadSettings() {
        const storedSettings = await GM_getValue(SETTINGS_KEY, DEFAULT_SETTINGS);
        settings = { ...DEFAULT_SETTINGS, ...storedSettings };
        isCurrentlyHidden = false; // Reset runtime state
        console.log("AC Script: Settings loaded:", settings);
    }

    async function saveSettings() {
        // Make sure to save all persistent settings
        const settingsToSave = {
            mode: settings.mode,
            numTurnsToShow: settings.numTurnsToShow,
            hideSidebars: settings.hideSidebars,
            hideSystemInstructions: settings.hideSystemInstructions,
            useLagFixInput: settings.useLagFixInput
        };
        await GM_setValue(SETTINGS_KEY, settingsToSave);
        console.log("AC Script: Settings saved:", settingsToSave);
    }

    // Update setting, save, and apply relevant rules
    function updateSetting(key, value) {
        if (settings[key] === value) return; // No change

        console.log(`AC Script: Setting ${key} changing to ${value}`);
        const previousMode = settings.mode;
        settings[key] = value;

        let needsChatRules = false;
        let needsLayoutRules = false;
        let needsObserverReinit = false;
        let needsPopupClose = false;

        if (key === 'mode') {
            needsChatRules = true;
            needsLayoutRules = true; // Mode change (esp. Vibe) affects layout
            needsObserverReinit = (value === 'auto' || previousMode === 'auto');
            needsPopupClose = true; // Close popup on mode change (radio or vibe button)
        } else if (key === 'numTurnsToShow') {
            if (settings.mode === 'manual' || settings.mode === 'auto') {
                needsChatRules = true; // Apply if relevant mode is active
            }
        } else if (key === 'hideSidebars' || key === 'hideSystemInstructions' || key === 'useLagFixInput') {
            needsLayoutRules = true; // These directly affect layout/input fix
        }

        saveSettings(); // Save any change

        if (needsLayoutRules) {
            applyLayoutRules(); // Apply layout changes (handles lag fix toggle too)
        }
        if (needsChatRules) {
            // Delay slightly after potential layout changes
            setTimeout(applyChatVisibilityRules, 50);
        }
        if (needsObserverReinit) {
            initChatObserver();
        }

        // Update popup UI state if it's open, *before* closing it
        if (popupElement?.style.display === 'block') {
            updatePopupUIState();
        }

        if (needsPopupClose) {
            hidePopup();
        }
    }

    // --- UI Elements (Button & Popup) ---
    function updateScriptToggleButtonAppearance() {
        if (!scriptToggleButton) return;
        const iconSpan = scriptToggleButton.querySelector('.material-symbols-outlined');
        if (iconSpan) {
            iconSpan.textContent = isCurrentlyHidden ? ICON_HIDDEN : ICON_VISIBLE;
        }
        const tooltipText = isCurrentlyHidden ? 'Chat history hidden (Click for options)' : 'Chat history visible (Click for options)';
        scriptToggleButton.setAttribute('aria-label', tooltipText);
        scriptToggleButton.setAttribute('mattooltip', tooltipText); // Attempt to update tooltip
        // Update Greasemonkey menu command text
        GM_registerMenuCommand(isCurrentlyHidden ? 'Show All History (via settings)' : 'Hide History (via settings)', togglePopup);
    }

    function createScriptToggleButton() {
        if (document.getElementById(SCRIPT_BUTTON_ID)) {
            scriptToggleButton = document.getElementById(SCRIPT_BUTTON_ID);
            updateScriptToggleButtonAppearance(); // Ensure icon is correct
            return;
        }
        const buttonContainer = document.querySelector(BUTTON_CONTAINER_SELECTOR);
        if (!buttonContainer) {
            console.error("AC Script: Could not find button container:", BUTTON_CONTAINER_SELECTOR); return;
        }
        console.log("AC Script: Creating settings button.");
        scriptToggleButton = document.createElement('button');
        scriptToggleButton.id = SCRIPT_BUTTON_ID;
        scriptToggleButton.className = 'mdc-icon-button mat-mdc-icon-button mat-unthemed mat-mdc-button-base gmat-mdc-button advanced-control-button';
        scriptToggleButton.style.marginLeft = '4px'; scriptToggleButton.style.marginRight = '4px';
        scriptToggleButton.style.order = '-1'; // Place first

        // --- FIX for TrustedHTML: Build elements manually ---
        const spanRipple = document.createElement('span');
        spanRipple.className = 'mat-mdc-button-persistent-ripple mdc-icon-button__ripple';
        scriptToggleButton.appendChild(spanRipple);

        const icon = document.createElement('span');
        icon.className = 'material-symbols-outlined notranslate';
        icon.setAttribute('aria-hidden', 'true');
        // Icon textContent (visibility/visibility_off) will be set by updateScriptToggleButtonAppearance
        scriptToggleButton.appendChild(icon);

        const focusIndicator = document.createElement('span');
        focusIndicator.className = 'mat-focus-indicator';
        scriptToggleButton.appendChild(focusIndicator);

        const touchTarget = document.createElement('span');
        touchTarget.className = 'mat-mdc-button-touch-target';
        scriptToggleButton.appendChild(touchTarget);
        // --- END FIX ---

        scriptToggleButton.addEventListener('click', togglePopup);
        buttonContainer.insertBefore(scriptToggleButton, buttonContainer.firstChild);
        updateScriptToggleButtonAppearance(); // Set initial icon/tooltip
        console.log("AC Script: Settings button added into", BUTTON_CONTAINER_SELECTOR);
    }
    function createPopupHtml() {
        const popup = document.createElement('div');
        popup.id = POPUP_ID;
        popup.className = 'advanced-control-popup';

        // --- Header ---
        const header = document.createElement('div');
        header.className = 'popup-header';

        const headerSpan = document.createElement('span');
        headerSpan.textContent = 'Advanced Controls'; // Use textContent
        header.appendChild(headerSpan);

        const closeButton = document.createElement('button');
        closeButton.type = 'button';
        closeButton.className = 'close-popup-button';
        closeButton.setAttribute('aria-label', 'Close settings');
        closeButton.textContent = '×'; // Use textContent
        closeButton.addEventListener('click', hidePopup);
        header.appendChild(closeButton);

        popup.appendChild(header);

        // --- Content Area ---
        const content = document.createElement('div');
        content.className = 'popup-content';

        // --- Vibe Mode Button ---
        const vibeButtonContainer = document.createElement('div');
        vibeButtonContainer.className = 'popup-section vibe-section';
        const vibeButton = document.createElement('button');
        vibeButton.id = 'vibe-mode-button';
        vibeButton.className = 'vibe-button';
        // Build button content manually
        const vibeIconSpan = document.createElement('span');
        vibeIconSpan.className = 'material-symbols-outlined';
        vibeIconSpan.textContent = ICON_VIBE;
        vibeButton.appendChild(vibeIconSpan);
        vibeButton.appendChild(document.createTextNode(' Activate VIBE Mode')); // Add text node
        vibeButton.addEventListener('click', () => updateSetting('mode', 'vibe'));
        vibeButtonContainer.appendChild(vibeButton);
        content.appendChild(vibeButtonContainer);

        // --- History Hiding Mode ---
        const historyGroup = document.createElement('fieldset');
        historyGroup.className = 'popup-section history-section';
        const historyLegend = document.createElement('legend');
        historyLegend.textContent = 'Chat History Mode:';
        historyGroup.appendChild(historyLegend);

        const modes = ['off', 'manual', 'auto'];
        const modeLabels = { off: 'Off (Show All)', manual: 'Manual Hide', auto: 'Auto Hide' };
        modes.forEach(modeValue => {
            const div = document.createElement('div');
            div.className = 'popup-setting radio-setting';

            const input = document.createElement('input');
            input.type = 'radio';
            input.name = 'history-mode-radio';
            input.id = `mode-${modeValue}-radio`;
            input.value = modeValue;
            input.addEventListener('change', (e) => {
                if (e.target.checked) updateSetting('mode', e.target.value);
            });

            const label = document.createElement('label');
            label.htmlFor = `mode-${modeValue}-radio`;
            label.textContent = modeLabels[modeValue];

            div.appendChild(input);
            div.appendChild(label);
            historyGroup.appendChild(div);
        });
        content.appendChild(historyGroup);

        // --- Number of Exchanges ---
        const numTurnsSetting = document.createElement('div');
        numTurnsSetting.className = 'popup-setting number-setting';

        const numLabel = document.createElement('label');
        numLabel.htmlFor = 'num-turns-input';
        numLabel.textContent = 'Keep Last:';
        numTurnsSetting.appendChild(numLabel);

        const numInput = document.createElement('input');
        numInput.type = 'number';
        numInput.id = 'num-turns-input';
        numInput.min = '0';
        numInput.addEventListener('change', (e) => {
            const num = parseInt(e.target.value, 10);
            const newValue = (!isNaN(num) && num >= 0) ? num : DEFAULT_SETTINGS.numTurnsToShow;
            updateSetting('numTurnsToShow', newValue);
            if (e.target.value !== newValue.toString()) e.target.value = newValue;
        });
        numTurnsSetting.appendChild(numInput);

        const numDescSpan = document.createElement('span');
        numDescSpan.id = 'num-turns-description';
        numDescSpan.textContent = 'Exchanges'; // Initial value
        numTurnsSetting.appendChild(numDescSpan);

        content.appendChild(numTurnsSetting);


        // --- UI Hiding Toggles ---
        const uiToggleGroup = document.createElement('fieldset');
        uiToggleGroup.className = 'popup-section ui-toggles-section';
        const uiLegend = document.createElement('legend');
        uiLegend.textContent = 'Interface Hiding:';
        uiToggleGroup.appendChild(uiLegend);

        const createToggle = (id, labelText, settingKey) => {
            const div = document.createElement('div');
            div.className = 'popup-setting toggle-setting';

            const label = document.createElement('label');
            label.htmlFor = id;
            label.className = 'toggle-label';
            label.textContent = labelText;

            const input = document.createElement('input');
            input.type = 'checkbox';
            input.id = id;
            input.className = 'basic-slide-toggle'; // Style with CSS
            input.addEventListener('change', (e) => updateSetting(settingKey, e.target.checked));

            div.appendChild(label); // Add label first
            div.appendChild(input); // Then add input (for styling purposes sometimes)
            uiToggleGroup.appendChild(div);
            // No need to return the input here as it's not used elsewhere directly
        };

        createToggle('hide-sidebars-toggle', 'Hide Sidebars', 'hideSidebars');
        createToggle('hide-sysinstruct-toggle', 'Hide System Instructions', 'hideSystemInstructions');
        createToggle('use-lagfix-toggle', 'Input Lag Fix', 'useLagFixInput');

        content.appendChild(uiToggleGroup);
        popup.appendChild(content);

        // --- Footer ---
        const footer = document.createElement('div');
        footer.className = 'popup-footer';
        const footerSpan = document.createElement('span');
        footerSpan.className = 'footer-note';
        footerSpan.textContent = 'Mode changes close panel. Toggles save instantly.';
        footer.appendChild(footerSpan);
        popup.appendChild(footer);

        return popup;
    }
    // Updates the state of controls within the popup
       // Updates the state of controls within the popup
    function updatePopupUIState() {
        if (!popupElement || popupElement.style.display === 'none') return;

        const isVibe = settings.mode === 'vibe';
        const isOff = settings.mode === 'off';

        // --- Update Vibe Button Appearance ---
        const vibeButton = popupElement.querySelector('#vibe-mode-button');
        if (vibeButton) {
            vibeButton.classList.toggle('active', isVibe);
            // Maybe change text when active?
            const iconSpan = vibeButton.querySelector('.material-symbols-outlined');
            const textNode = vibeButton.lastChild; // Assuming text is last child
            if (isVibe && textNode.nodeType === Node.TEXT_NODE) {
                textNode.textContent = ' VIBE MODE ACTIVE';
                if (iconSpan) iconSpan.textContent = 'check_circle'; // Show checkmark?
            } else if (textNode.nodeType === Node.TEXT_NODE){
                textNode.textContent = ' Activate VIBE Mode';
                if (iconSpan) iconSpan.textContent = ICON_VIBE; // Restore original icon
            }
        }


        // --- Update History Mode Radio Buttons ---
        popupElement.querySelectorAll('input[name="history-mode-radio"]').forEach(radio => {
             radio.checked = (settings.mode === radio.value);
             // --- FIX: DO NOT disable radios when Vibe is active ---
             // Radios should always be clickable to exit Vibe mode
             radio.disabled = false;
        });

        // --- Update Number Input ---
        const numInput = popupElement.querySelector('#num-turns-input');
        const numDesc = popupElement.querySelector('#num-turns-description');
        if (numInput) {
            numInput.value = settings.numTurnsToShow;
            // Disable number input only if mode is Off OR Vibe
            numInput.disabled = isOff || isVibe;
        }
         if(numDesc) {
             numDesc.textContent = (settings.mode === 'manual' || settings.mode === 'auto') ? 'Exchanges (User+AI)' : ' ';
             // Hide description if number input is disabled
             numDesc.style.display = (isOff || isVibe) ? 'none' : '';
         }

        // --- Update UI Toggles State and Disabled Status ---
        const updateToggleUI = (id, settingKey) => {
            const toggle = popupElement.querySelector(`#${id}`);
            if (toggle) {
                toggle.checked = settings[settingKey];
                // FIX: Disable UI toggles ONLY if Vibe mode is active
                toggle.disabled = isVibe;
                 // Also visually grey out the label if disabled
                 const label = popupElement.querySelector(`label[for="${id}"]`);
                 if (label) label.style.opacity = isVibe ? '0.5' : '1';
            }
        };
        updateToggleUI('hide-sidebars-toggle', 'hideSidebars');
        updateToggleUI('hide-sysinstruct-toggle', 'hideSystemInstructions');
        updateToggleUI('use-lagfix-toggle', 'useLagFixInput');

    }
    function showPopup() {
        // ... (Similar to v3.0, creates popup if needed, updates UI, positions, adds listener) ...
        if (!scriptToggleButton) return;
        if (!popupElement) {
            popupElement = createPopupHtml();
            document.body.appendChild(popupElement);
        }
        updatePopupUIState(); // Ensure UI reflects current settings

        const buttonRect = scriptToggleButton.getBoundingClientRect();
        popupElement.style.top = `${buttonRect.bottom + window.scrollY + 5}px`;
        popupElement.style.left = 'auto';
        popupElement.style.right = `${window.innerWidth - buttonRect.right - window.scrollX}px`;
        popupElement.style.display = 'block';
        console.log("AC Script: Popup shown.");
        setTimeout(() => {
            document.addEventListener('click', handleClickOutsidePopup, { capture: true, once: true });
        }, 0);
    }

    function hidePopup() {
        // ... (Similar to v3.0) ...
        if (popupElement) {
            popupElement.style.display = 'none';
            document.removeEventListener('click', handleClickOutsidePopup, { capture: true });
            console.log("AC Script: Popup hidden.");
        }
    }

    function togglePopup(event) {
        // ... (Similar to v3.0) ...
        if (event) event.stopPropagation();
        if (popupElement?.style.display === 'block') { hidePopup(); }
        else { showPopup(); }
    }

    function handleClickOutsidePopup(event) {
        // ... (Similar to v3.0, but check scriptToggleButton too) ...
        if (popupElement?.style.display === 'block' &&
            !popupElement.contains(event.target) &&
            scriptToggleButton && !scriptToggleButton.contains(event.target)) {
            console.log("AC Script: Clicked outside popup.");
            hidePopup();
        } else if (popupElement?.style.display === 'block') {
            // Re-add listener if click was inside
            document.addEventListener('click', handleClickOutsidePopup, { capture: true, once: true });
        }
    }

    // --- Input Lag Fix Logic ---
          // --- Input Lag Fix Logic ---
    function toggleLagFixInput(activate) {
        // Ensure we have the real elements cached or find them
        if (!realChatInput) realChatInput = document.querySelector(CHAT_INPUT_SELECTOR);
        if (!realRunButton) realRunButton = document.querySelector(RUN_BUTTON_SELECTOR);

        if (activate) {
            // --- Activate Lag Fix ---
            if (!realChatInput || !realRunButton) {
                console.error("AC Script: Cannot activate Lag Fix - Real Input or Run button not found! Verify selectors:", CHAT_INPUT_SELECTOR, RUN_BUTTON_SELECTOR);
                 if(settings.useLagFixInput || settings.mode === 'vibe') {
                     if(settings.useLagFixInput) updateSetting('useLagFixInput', false);
                 }
                return; // Stop activation
            }

            // Check if fake elements already exist to prevent duplicates
            const existingFakeInput = document.getElementById(FAKE_INPUT_ID);
            const existingFakeButton = document.getElementById(FAKE_RUN_BUTTON_ID);

            if (!existingFakeInput) { // Only create if it doesn't exist
                console.log("AC Script: Activating Lag Fix Input.");
                try {
                    // --- Hide Real Input ---
                    realChatInput.classList.add('adv-controls-real-input-hidden');

                    // --- Create Fake Input ---
                    fakeChatInput = document.createElement('textarea'); // Use the state variable
                    fakeChatInput.id = FAKE_INPUT_ID;
                    fakeChatInput.className = 'advanced-control-fake-input'; // Class for styling via addStyles
                    fakeChatInput.setAttribute('placeholder', realChatInput.getAttribute('placeholder') || 'Type something...');
                    fakeChatInput.setAttribute('rows', realChatInput.getAttribute('rows') || '1'); // Copy rows attribute
                    // Apply necessary styles directly for layout matching
                    const computedStyle = window.getComputedStyle(realChatInput);
                    fakeChatInput.style.height = computedStyle.height;
                    fakeChatInput.style.resize = computedStyle.resize;
                    fakeChatInput.style.overflow = computedStyle.overflow;
                    fakeChatInput.style.width = '100%';

                    // Insert fake input before the real one
                    realChatInput.parentNode.insertBefore(fakeChatInput, realChatInput);

                    // Explicitly focus the fake input
                     setTimeout(() => fakeChatInput.focus(), 100);

                } catch (error) {
                    console.error("AC Script: Error creating fake input:", error);
                    // Attempt cleanup if input creation failed
                    realChatInput?.classList.remove('adv-controls-real-input-hidden');
                    if(fakeChatInput) fakeChatInput.remove(); // Remove partially created element
                    fakeChatInput = null; // Reset state variable
                    return; // Stop activation
                }
            } else {
                // If fake input exists ensure it gets focus
                fakeChatInput = existingFakeInput; // Ensure state variable is correct
                setTimeout(() => fakeChatInput.focus(), 100);
            }

            if (!existingFakeButton && fakeChatInput) { // Only create button if it doesn't exist AND fake input exists
                 console.log("AC Script: Creating Fake Run Button.");
                 try {
                     const fakeRunButton = document.createElement('button');
                     fakeRunButton.id = FAKE_RUN_BUTTON_ID;
                     fakeRunButton.className = 'advanced-control-fake-run-button'; // Style with CSS
                     fakeRunButton.textContent = 'Run (Lag Fix)'; // Indicate it's the fake one
                     fakeRunButton.type = 'button'; // Prevent default form submission if any
                     fakeRunButton.addEventListener('click', handleFakeRunButtonClick); // Use a dedicated handler

                     // Insert the fake button - try inserting it *after* the fake input's container div
                     // Adjust this based on where the original Run button visually is relative to the textarea
                     // Assuming the real input and button share a common parent wrapper:
                     const inputWrapper = realChatInput.closest('.prompt-input-wrapper-container') || realChatInput.parentNode; // Find a suitable parent
                     const buttonContainer = inputWrapper.querySelector('.button-wrapper:last-of-type'); // Find the original button's wrapper
                     if (buttonContainer) {
                         buttonContainer.parentNode.insertBefore(fakeRunButton, buttonContainer.nextSibling); // Insert after button wrapper
                         // Hide the original button's wrapper visually
                         buttonContainer.style.display = 'none';
                     } else {
                         // Fallback: Insert after the fake input if wrapper not found
                         fakeChatInput.parentNode.insertBefore(fakeRunButton, fakeChatInput.nextSibling);
                     }
                      console.log("AC Script: Fake Run button added.");
                 } catch (error) {
                     console.error("AC Script: Error creating fake run button:", error);
                     // Don't necessarily stop activation, input might still work manually
                 }
            }

        } else {
            // --- Deactivate Lag Fix ---
            console.log("AC Script: Deactivating Lag Fix Input.");
            const existingFakeInput = document.getElementById(FAKE_INPUT_ID);
            if (existingFakeInput) {
                existingFakeInput.remove();
            }
            fakeChatInput = null; // Reset state

            const existingFakeButton = document.getElementById(FAKE_RUN_BUTTON_ID);
            if (existingFakeButton) {
                existingFakeButton.remove();
            }

            // Restore real input
            if (realChatInput) {
                realChatInput.classList.remove('adv-controls-real-input-hidden');
            }

            // Restore original button wrapper's visibility if we hid it
             if(realRunButton){
                 const inputWrapper = realChatInput.closest('.prompt-input-wrapper-container') || realChatInput.parentNode;
                 const buttonContainer = inputWrapper.querySelector('.button-wrapper:has(run-button)'); // Find original button container
                 if (buttonContainer) buttonContainer.style.display = ''; // Restore display
             }
        }
    }
       // --- Handler for the FAKE Run Button ---
    function handleFakeRunButtonClick(event) {
        // --- Ensure we have references to the necessary elements ---
        // Re-query just in case, although they should be cached if lag fix is active
        const currentFakeInput = document.getElementById(FAKE_INPUT_ID);
        const currentRealInput = realChatInput || document.querySelector(CHAT_INPUT_SELECTOR);
        const currentRealRunButton = realRunButton || document.querySelector(RUN_BUTTON_SELECTOR);

        if (currentFakeInput && currentRealInput && currentRealRunButton) {
            console.log("AC Script: FAKE Run Button Clicked! Attempting submit.");

            // 1. Copy text from fake to real
            const textToSubmit = currentFakeInput.value;
            if (!textToSubmit.trim()) {
                console.log("AC Script: Fake input is empty, doing nothing.");
                return; // Don't submit if empty
            }
            currentRealInput.value = textToSubmit;
            console.log("AC Script: Copied text to real input.");

            // 2. Trigger events on real input to make the site aware
            try {
                currentRealInput.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
                currentRealInput.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
                console.log("AC Script: Dispatched input/change events on real input.");
            } catch (e) {
                console.error("AC Script: Error dispatching events on real input:", e);
                // Don't necessarily stop here, try clicking anyway? Maybe comment out return.
                // return; // Optional: Stop if events fail
            }

            // 3. Clear the fake input (do this AFTER potentially needing focus)
            // currentFakeInput.value = ''; // Let's clear it AFTER the click attempt

            // 4. Ensure the REAL input potentially has focus briefly before the click might need it
            // Although clicking the button should be sufficient usually
             currentRealInput.focus(); // Try focusing the real input briefly
             currentRealInput.blur(); // Then blur it, sometimes helps trigger validation

            // 5. Force Enable the REAL Run Button
            let wasDisabled = false;
            if (currentRealRunButton.disabled) {
                currentRealRunButton.disabled = false;
                wasDisabled = true;
                console.log("AC Script: Force removed 'disabled' attribute from Real Run button.");
            }
            // NOTE: Add class removal here if necessary, based on inspecting the disabled state

            // 6. Programmatically click the REAL Run Button
            // Use a timeout to allow potential UI updates after events/focus/enable
            setTimeout(() => {
                 console.log("AC Script: Programmatically clicking REAL Run button.");
                 if (currentRealRunButton.offsetParent === null) { // Check if button is actually visible
                      console.warn("AC Script: Real run button is not visible/in DOM just before click?");
                       if (wasDisabled) currentRealRunButton.disabled = true; // Re-disable if we couldn't click
                      return;
                 }

                 // --- THE ACTUAL CLICK ---
                 currentRealRunButton.click();
                 // --- END CLICK ---

                 console.log("AC Script: Click dispatched on real button.");

                 // Clear the fake input now
                 currentFakeInput.value = '';


                 // Optional: Re-disable immediately after clicking? Less critical now.
                 // if (wasDisabled) {
                 //     setTimeout(() => { currentRealRunButton.disabled = true; }, 10);
                 // }

            }, 150); // Slightly increased delay again to 150ms, just in case

        } else {
            console.warn("AC Script: Fake Run Button click failed - elements missing.",
                { currentFakeInput, currentRealInput, currentRealRunButton }); // Log which element might be missing
        }
    }

    // --- Mutation Observer (for Auto mode chat hiding) ---
    function handleChatMutation(mutationsList, observer) {
        // ... (Similar logic to v3.0, checking for added AI turns) ...
        if (settings.mode !== 'auto') return;
        let newAiTurnAdded = false;
        // ... (rest of mutation checking logic) ...

        if (newAiTurnAdded) {
            clearTimeout(debounceTimer);
            debounceTimer = setTimeout(() => {
                console.log("AC Script: Auto mode applying chat rules.");
                applyChatVisibilityRules();
                const chatContainer = document.querySelector(CHAT_CONTAINER_SELECTOR);
                if(chatContainer) setTimeout(() => chatContainer.scrollTop = chatContainer.scrollHeight, 50);
            }, 300);
        }
    }

    function initChatObserver() {
        // ... (Similar logic to v3.0, starting/stopping based on settings.mode === 'auto') ...
        if (chatObserver) { chatObserver.disconnect(); chatObserver = null; }
        if (settings.mode === 'auto') {
            const chatContainer = document.querySelector(CHAT_CONTAINER_SELECTOR);
            if (chatContainer) {
                chatObserver = new MutationObserver(handleChatMutation);
                chatObserver.observe(chatContainer, { childList: true, subtree: true });
                console.log("AC Script: Chat observer started for auto-hide.");
            } else { console.warn("AC Script: Could not find chat container for observer."); }
        } else { console.log("AC Script: Chat observer inactive."); }
    }

    // --- Initialization & Styles ---
    function addStyles() {
        // --- APPROXIMATE DARK THEME ---
        // These colors are guesses based on common dark themes.
        // Replace with exact values if you find them via Inspector.
        const darkBg = '#202124';// Main dark background
        const lighterDarkBg = '#303134'; // Slightly lighter background (e.g., popup header/footer)
        const lightText = '#e8eaed';// Main light text
        const mediumText = '#bdc1c6';// Secondary text (e.g., descriptions)
        const darkBorder = '#5f6368';// Borders
        const accentColor = '#8ab4f8';// Accent color (e.g., toggle ON state, buttons) - Google blueish
        const handleColor = '#e8eaed';// Toggle handle color
        const trackOffColor = '#5f6368';// Toggle track OFF color
        const inputBg = '#3c4043';// Input field background

        GM_addStyle(`
            /* --- General Popup Styling (Dark Theme Approx) --- */
            :root { /* Define CSS variables for easier reuse */
                --popup-bg: ${darkBg};
                --popup-header-bg: ${lighterDarkBg};
                --popup-footer-bg: ${lighterDarkBg};
                --popup-text-primary: ${lightText};
                --popup-text-secondary: ${mediumText};
                --popup-border: ${darkBorder};
                --input-bg: ${inputBg};
                --input-border: ${darkBorder};
                --input-text: ${lightText};
                --accent-color: ${accentColor};
                --toggle-handle-color: ${handleColor};
                --toggle-track-off-color: ${trackOffColor};
                --textarea-bg-color: ${inputBg}; /* For fake input */
                --textarea-text-color: ${lightText}; /* For fake input */
                --textarea-border-color: ${darkBorder}; /* For fake input */
            }
                        /* --- Hiding Real Input for Lag Fix --- */
            .adv-controls-real-input-hidden {
                visibility: hidden !important;
                position: absolute !important; /* Take out of flow */
                height: 1px !important;
                width: 1px !important;
                overflow: hidden !important;
                border: none !important;
                padding: 0 !important;
                margin: 0 !important;
                opacity: 0 !important;
             }

             /* --- Fake Input Basic Styling (Refined) --- */
             #${FAKE_INPUT_ID}.advanced-control-fake-input {
                 /* Styles copied dynamically, use CSS vars */
                 background-color: var(--textarea-bg-color);
                 color: var(--textarea-text-color);
                 border: 1px solid var(--textarea-border-color);
                 padding: 10px; /* Example padding, adjust if needed based on real input */
                 border-radius: 4px; /* Example radius */
                 font-family: inherit; /* Inherit from container */
                 font-size: inherit; /* Inherit from container */
                 line-height: 1.5; /* Example line-height */
                 display: block; /* Ensure block layout */
                 box-sizing: border-box;
                 margin: 0; /* Reset margin */
                 /* Width and height set dynamically via JS */
                 /* Ensure transitions don't interfere */
                 transition: none !important;
             }


            #${POPUP_ID} {
                display: none; position: absolute; z-index: 10001;
                background-color: var(--popup-bg);
                border: 1px solid var(--popup-border);
                border-radius: 8px;
                box-shadow: 0 4px 8px 3px rgba(0,0,0,0.3); /* Darker shadow */
                width: 340px; /* Slightly wider */
                font-family: "Google Sans", Roboto, Arial, sans-serif; /* Verify Font */
                font-size: 14px;
                color: var(--popup-text-primary);
                overflow: hidden;
            }
            #${POPUP_ID} .popup-header {
                display: flex; justify-content: space-between; align-items: center;
                padding: 12px 16px; border-bottom: 1px solid var(--popup-border);
                font-weight: 500; font-size: 16px;
                background-color: var(--popup-header-bg);
            }
            #${POPUP_ID} .close-popup-button {
                background: none; border: none; font-size: 24px; line-height: 1;
                cursor: pointer; color: var(--popup-text-secondary); padding: 0 4px; margin: -4px;
            }
            #${POPUP_ID} .close-popup-button:hover { color: var(--popup-text-primary); }
            #${POPUP_ID} .popup-content { padding: 16px; display: flex; flex-direction: column; gap: 16px; }
            #${POPUP_ID} .popup-section { border: none; padding: 0; margin: 0; }
            #${POPUP_ID} legend { font-weight: 500; padding-bottom: 8px; color: var(--popup-text-primary); border-bottom: 1px solid var(--popup-border); margin-bottom: 8px; }

            /* --- Vibe Button --- */
             #${POPUP_ID} .vibe-section { margin-bottom: 10px; border-bottom: 1px solid var(--popup-border); padding-bottom: 15px;}
             #${POPUP_ID} .vibe-button {
                 display: flex; align-items: center; justify-content: center; gap: 8px;
                 width: 100%; padding: 10px 16px; font-size: 15px; font-weight: 500;
                 border: 1px solid var(--popup-border); border-radius: 4px; cursor: pointer;
                 background-color: var(--popup-bg); color: var(--popup-text-primary);
                 transition: background-color 0.2s, border-color 0.2s;
             }
            #${POPUP_ID} .vibe-button:hover { background-color: ${lighterDarkBg}; border-color: var(--accent-color); }
            #${POPUP_ID} .vibe-button.active { background-color: var(--accent-color); color: ${darkBg}; border-color: var(--accent-color); }
            #${POPUP_ID} .vibe-button .material-symbols-outlined { font-size: 20px; }

            /* --- Settings Items --- */
            #${POPUP_ID} .popup-setting { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
            #${POPUP_ID} .popup-setting label { cursor: pointer; user-select: none; }
            #${POPUP_ID} input[type="radio"] { accent-color: var(--accent-color); cursor: pointer; width: 16px; height: 16px; margin: 0;}
            #${POPUP_ID} input[type="radio"]:disabled + label { color: var(--popup-text-secondary); cursor: not-allowed; }
             #${POPUP_ID} input:disabled { cursor: not-allowed; opacity: 0.6; }

            #${POPUP_ID} .number-setting label { white-space: nowrap; }
            #${POPUP_ID} input[type="number"] {
                width: 60px; padding: 6px 8px; border-radius: 4px; text-align: right;
                background-color: var(--input-bg); color: var(--input-text); border: 1px solid var(--input-border);
            }
            #${POPUP_ID} input[type="number"]:disabled { background-color: ${darkBg}; border-color: ${darkBorder}; opacity: 0.5; }
            #${POPUP_ID} #num-turns-description { color: var(--popup-text-secondary); font-size: 13px; }

            /* --- Basic Slide Toggle Styling (Approximate) --- */
            #${POPUP_ID} .toggle-setting { justify-content: space-between; } /* Push toggle right */
            #${POPUP_ID} .toggle-label { flex-grow: 1; } /* Allow label to take space */
            #${POPUP_ID} .basic-slide-toggle {
                appearance: none; -webkit-appearance: none; position: relative;
                width: 36px; height: 20px; border-radius: 10px;
                background-color: var(--toggle-track-off-color);
                cursor: pointer; transition: background-color 0.2s ease-in-out;
                display: inline-block; vertical-align: middle;
            }
            #${POPUP_ID} .basic-slide-toggle::before { /* The Handle */
                content: ''; position: absolute;
                width: 16px; height: 16px; border-radius: 50%;
                background-color: var(--toggle-handle-color);
                top: 2px; left: 2px;
                transition: transform 0.2s ease-in-out;
                box-shadow: 0 1px 3px rgba(0,0,0,0.4);
            }
            #${POPUP_ID} .basic-slide-toggle:checked {
                background-color: var(--accent-color);
            }
            #${POPUP_ID} .basic-slide-toggle:checked::before {
                transform: translateX(16px); /* Move handle right */
            }
            #${POPUP_ID} .basic-slide-toggle:disabled { opacity: 0.5; cursor: not-allowed; }
            #${POPUP_ID} .basic-slide-toggle:disabled::before { background-color: ${mediumText}; }


            /* --- Footer --- */
            #${POPUP_ID} .popup-footer {
                padding: 8px 16px; border-top: 1px solid var(--popup-border); font-size: 12px;
                color: var(--popup-text-secondary); text-align: center;
                background-color: var(--popup-footer-bg);
            }

            /* --- UI Hiding Classes --- */
            /* Apply these classes to OVERALL_LAYOUT_SELECTOR */
            .${LAYOUT_HIDE_CLASS}-sidebars ${LEFT_SIDEBAR_SELECTOR},
            .${LAYOUT_HIDE_CLASS}-sidebars ${RIGHT_SIDEBAR_SELECTOR} {
                display: none !important;
            }
            .${LAYOUT_HIDE_CLASS}-sysinstruct ${SYSTEM_INSTRUCTIONS_SELECTOR} {
                 display: none !important;
            }

             /* --- Fake Input Styling --- */
             #${FAKE_INPUT_ID} {
                 /* Styles copied in JS, use CSS vars */
                 background-color: var(--textarea-bg-color);
                 color: var(--textarea-text-color);
                 border: 1px solid var(--textarea-border-color);
                 display: block; /* Ensure it takes block layout */
                 /* Ensure transitions don't interfere if original had them */
                 transition: none !important;
             }
                         /* --- Fake Run Button Styling --- */
            #${FAKE_RUN_BUTTON_ID}.advanced-control-fake-run-button {
                /* Style similarly to the real run button */
                background-color: var(--accent-color); /* Use accent color */
                color: var(--popup-bg); /* Dark text on light button */
                border: none;
                border-radius: 4px; /* Match real button radius */
                padding: 8px 16px; /* Adjust padding */
                margin-left: 8px; /* Space from input */
                font-size: 14px; /* Match real button */
                font-weight: 500; /* Match real button */
                cursor: pointer;
                transition: background-color 0.2s;
            }
            #${FAKE_RUN_BUTTON_ID}.advanced-control-fake-run-button:hover {
                opacity: 0.9; /* Simple hover effect */
            }

        `);
    }

    // Utility to wait for an element
    function waitForElement(selector, callback, checkFrequency = 300, timeout = 15000) {
        // ... (same as before) ...
        const startTime = Date.now();
        const interval = setInterval(() => {
            const element = document.querySelector(selector);
            if (element) {
                clearInterval(interval); callback(element);
            } else if (Date.now() - startTime > timeout) {
                console.error(`AC Script: Timeout waiting for element: ${selector}`); clearInterval(interval); callback(null); // Indicate failure
            }
        }, checkFrequency);
        return interval;
    }

    // --- Main Initialization Sequence ---
    async function initialize() {
        console.log("AC Script: Initializing Advanced Control Suite v4.0...");
        addStyles();
        await loadSettings(); // Load settings first

        // Use Promise.allSettled to wait for multiple elements, some might timeout
        Promise.allSettled([
            new Promise((resolve, reject) => waitForElement(BUTTON_CONTAINER_SELECTOR, resolve, 150, 10000)),
            new Promise((resolve, reject) => waitForElement(CHAT_CONTAINER_SELECTOR, resolve, 300, 15000)),
            new Promise((resolve, reject) => waitForElement(OVERALL_LAYOUT_SELECTOR, resolve, 300, 15000)) // Wait for layout container too
        ]).then(results => {
            const buttonContainerResult = results[0];
            const chatContainerResult = results[1];
            const layoutContainerResult = results[2];

            if (buttonContainerResult.status === 'fulfilled' && buttonContainerResult.value) {
                console.log("AC Script: Button container found.");
                createScriptToggleButton(); // Create the main button
            } else {
                console.error("AC Script: Button container not found. UI button cannot be added.");
            }

            if (chatContainerResult.status === 'fulfilled' && chatContainerResult.value) {
                console.log("AC Script: Chat container found.");
                // Apply initial chat rules
                applyChatVisibilityRules();
                // Initialize the chat observer based on loaded settings
                initChatObserver();
            } else {
                console.warn("AC Script: Chat container not found. History features may fail.");
            }

            if (layoutContainerResult.status === 'fulfilled' && layoutContainerResult.value) {
                console.log("AC Script: Layout container found.");
                // Apply initial layout rules (hiding UI elements, activating lag fix if needed)
                applyLayoutRules();
            } else {
                console.warn(`AC Script: Layout container (${OVERALL_LAYOUT_SELECTOR}) not found. UI hiding features may fail.`);
            }

            console.log("AC Script: Initial setup attempted.");

            // Pre-cache input/run button for lag fix if setting is initially true
            if(settings.useLagFixInput || settings.mode === 'vibe'){
                waitForElement(CHAT_INPUT_SELECTOR, el => { if(el) realChatInput = el; console.log("AC Script: Cached real chat input."); }, 500, 10000);
                waitForElement(RUN_BUTTON_SELECTOR, el => { if(el) realRunButton = el; console.log("AC Script: Cached real run button."); }, 500, 10000);
            }

        });

        // Register menu command regardless
        GM_registerMenuCommand('Adv. Control Settings (AI Studio)', togglePopup);
    }

    // --- Start Execution ---
    // Wait for window load to maximize chance of elements being ready
    window.addEventListener('load', initialize);

})();