LibreChat Shortcuts + Token Counter

Press Alt+S to click toggle-left-nav button on localhost:3080

// ==UserScript==
// @name         LibreChat Shortcuts + Token Counter
// @namespace    http://tampermonkey.net/
// @version      2.8.5
// @description  Press Alt+S to click toggle-left-nav button on localhost:3080
// @author       bwhurd
// @match        http://localhost:3080/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

// === Shortcut Keybindings ===
// Alt+S → Toggle sidebar (clicks #toggle-left-nav)
// Alt+N → New chat (clicks button[aria-label="New chat"])
// Alt+T → Scroll to top of message container
// Alt+Z → Scroll to bottom of message container
// Alt+W → Focus Chat Input
// Alt+C → Click Copy on lowest message
// Alt+X → Select and copy, cycles visible messages
// Alt+A → Scroll up one message (.message-render)
// Alt+F → Scroll down one message (.message-render)
// Just start typing to go to input chatbox
// Paste input when not in chat box

// Alt+R → refresh cost for conversation
// Alt+U → update the token cost per million
// Alt+e   →   toggle collapse expand chat
// alt+d   →   Open the preset menu to see the "defaults"
// alt+# 1-9 to activate presets
// Click right sidebar toggle with alt+g for game settings


// other fixes
// label presets with alt+1 to alt+9
// Convert <br> in tables displaying as literal <br> to line breaks

// toggle footer text
(function () {
    /* Creates a global CSS rule that hides:
     1. Anything with role="contentinfo"
     2. Any <button> whose aria-label is exactly "Code Interpreter"           */
    const style = document.createElement('style');
    style.textContent = `
    [role="contentinfo"],
    button[aria-label="Code Interpreter"] {
      display: none !important;
    }
  `;
    document.head.appendChild(style);
})();

(function () {
    'use strict';

    // === Inject custom CSS to override hidden footer button color ===
    const style = document.createElement('style');
    style.textContent = `
        .relative.hidden.items-center.justify-center {
            display:none;
        }
    `;
    document.head.appendChild(style);

    // Shared scroll state object
    const ScrollState = {
        scrollContainer: null,
        isAnimating: false,
        finalScrollPosition: 0,
        userInterrupted: false,
    };

    function resetScrollState() {
        if (ScrollState.isAnimating) {
            ScrollState.isAnimating = false;
            ScrollState.userInterrupted = true;
        }
        ScrollState.scrollContainer = getScrollableContainer();
        if (ScrollState.scrollContainer) {
            ScrollState.finalScrollPosition = ScrollState.scrollContainer.scrollTop;
        }
    }

    function getScrollableContainer() {
        const firstMessage = document.querySelector('.message-render');
        if (!firstMessage) return null;

        let container = firstMessage.parentElement;
        while (container && container !== document.body) {
            const style = getComputedStyle(container);
            if (
                container.scrollHeight > container.clientHeight &&
                style.overflowY !== 'visible' &&
                style.overflowY !== 'hidden'
            ) {
                return container;
            }
            container = container.parentElement;
        }

        return document.scrollingElement || document.documentElement;
    }

    function checkGSAP() {
        if (
            typeof window.gsap !== "undefined" &&
            typeof window.ScrollToPlugin !== "undefined" &&
            typeof window.Observer !== "undefined" &&
            typeof window.Flip !== "undefined"
        ) {
            gsap.registerPlugin(ScrollToPlugin, Observer, Flip);
            console.log("✅ GSAP and plugins registered");
            initShortcuts();
        } else {
            console.warn("⏳ GSAP not ready. Retrying...");
            setTimeout(checkGSAP, 100);
        }
    }

    function loadGSAPLibraries() {
        const libs = [
            'https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.7/gsap.min.js',
            'https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.7/ScrollToPlugin.min.js',
            'https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.7/Observer.min.js',
            'https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.7/Flip.min.js',
        ];

        libs.forEach(src => {
            const script = document.createElement('script');
            script.src = src;
            script.async = false;
            document.head.appendChild(script);
        });

        checkGSAP();
    }

    function scrollToTop() {
        const container = getScrollableContainer();
        if (!container) return;
        gsap.to(container, {
            duration: .6,
            scrollTo: { y: 0 },
            ease: "power4.out"
        });
    }

    function scrollToBottom() {
        const container = getScrollableContainer();
        if (!container) return;
        gsap.to(container, {
            duration: .6,
            scrollTo: { y: "max" },
            ease: "power4.out"
        });
    }

    function scrollUpOneMessage() {
        const container = getScrollableContainer();
        if (!container) return;

        const messages = [...document.querySelectorAll('.message-render')];
        const currentScrollTop = container.scrollTop;

        let target = null;
        for (let i = messages.length - 1; i >= 0; i--) {
            if (messages[i].offsetTop < currentScrollTop - 25) {
                target = messages[i];
                break;
            }
        }

        gsap.to(container, {
            duration: 0.6,
            scrollTo: { y: target?.offsetTop || 0 },
            ease: "power4.out"
        });
    }

    function scrollDownOneMessage() {
        const container = getScrollableContainer();
        if (!container) return;

        const messages = [...document.querySelectorAll('.message-render')];
        const currentScrollTop = container.scrollTop;

        let target = null;
        for (let i = 0; i < messages.length; i++) {
            if (messages[i].offsetTop > currentScrollTop + 25) {
                target = messages[i];
                break;
            }
        }

        gsap.to(container, {
            duration: 0.6,
            scrollTo: { y: target?.offsetTop || container.scrollHeight },
            ease: "power4.out"
        });
    }

    function initShortcuts() {
        document.addEventListener('keydown', function (e) {
            if (!e.altKey || e.repeat) return;

            const key = e.key.toLowerCase();

            const keysToBlock = ['s', 'n', 't', 'z', 'a', 'f'];
            if (keysToBlock.includes(key)) {
                e.preventDefault();
                e.stopPropagation();

                switch (key) {
                    case 's': toggleSidebar(); break;
                    case 'n': openNewChat(); break;
                    case 't': scrollToTop(); break;
                    case 'z': scrollToBottom(); break;
                    case 'a': scrollUpOneMessage(); break;
                    case 'f': scrollDownOneMessage(); break;
                }
            }
        });

        console.log("✅ LibreChat shortcuts active");
    }

    function toggleSidebar() {
        const selectors = [
            '[data-testid="close-sidebar-button"]',
            '[data-testid="open-sidebar-button"]'
        ];

        for (const selector of selectors) {
            const btn = document.querySelector(selector);
            if (btn) {
                btn.click();
                console.log(`🧭 Sidebar toggled via ${selector}`);
                return;
            }
        }

        console.warn('⚠️ No sidebar toggle button found');
    }


    function openNewChat() {
        const newChatButton = document.querySelector('button[aria-label="New chat"]');
        if (newChatButton) {
            newChatButton.click();
            console.log('🆕 New chat opened');
        }
    }

    // Start loading GSAP plugins and wait for them
    loadGSAPLibraries();
})();




(function() {
    document.addEventListener('keydown', function(e) {
        if (e.altKey && e.key === 'w') {
            e.preventDefault();
            const chatInput = document.querySelector('#prompt-textarea');
            if (chatInput) {
                chatInput.focus();
            }
        }
    });
})();

(function() {
    function removeMarkdown(text) {
        return text
        // Remove bold/italics
            .replace(/(\*\*|__)(.*?)\1/g, "$2")
            .replace(/(\*|_)(.*?)\1/g, "$2")
        // Remove leading '#' from headers
            .replace(/^#{1,6}\s+(.*)/gm, "$1")
        // Preserve indentation for unordered list items
            .replace(/^(\s*)[\*\-\+]\s+(.*)/gm, "$1- $2")
        // Preserve indentation for ordered list items
            .replace(/^(\s*)(\d+)\.\s+(.*)/gm, "$1$2. $3")
        // Remove triple+ line breaks
            .replace(/\n{3,}/g, "\n\n")
            .trim();
    }

    document.addEventListener('keydown', function(e) {
        if (e.altKey && e.key === 'c') {
            e.preventDefault();
            const allButtons = Array.from(document.querySelectorAll('button'));
            const visibleButtons = allButtons.filter(button =>
                                                     button.innerHTML.includes('M7 5a3 3 0 0 1 3-3h9a3')
                                                    ).filter(button => {
                const rect = button.getBoundingClientRect();
                return (
                    rect.top >= 0 &&
                    rect.left >= 0 &&
                    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
                    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
                );
            });

            if (visibleButtons.length > 0) {
                visibleButtons[visibleButtons.length - 1].click();

                setTimeout(() => {
                    if (!navigator.clipboard) return;

                    navigator.clipboard.readText()
                        .then(textContent => navigator.clipboard.writeText(removeMarkdown(textContent)))
                        .then(() => console.log("Markdown removed and copied."))
                        .catch(() => {});
                }, 500);
            }
        }
    });
})();

(function() {
    // Initialize single global store for last selection
    window.selectAllLowestResponseState = window.selectAllLowestResponseState || {
        lastSelectedIndex: -1
    };

    document.addEventListener('keydown', function(e) {
        if (e.altKey && e.key === 'x') {
            e.preventDefault();
            // Delay execution to ensure DOM is fully loaded
            setTimeout(() => {
                try {
                    const onlySelectAssistant = window.onlySelectAssistantCheckbox || false;
                    const onlySelectUser = window.onlySelectUserCheckbox || false;
                    const disableCopyAfterSelect = window.disableCopyAfterSelectCheckbox || false;

                    const allConversationTurns = (() => {
                        try {
                            return Array.from(document.querySelectorAll('.user-turn, .agent-turn')) || [];
                        } catch {
                            return [];
                        }
                    })();

                    const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
                    const viewportWidth = window.innerWidth || document.documentElement.clientWidth;

                    const composerRect = (() => {
                        try {
                            const composerBackground = document.getElementById('composer-background');
                            return composerBackground ? composerBackground.getBoundingClientRect() : null;
                        } catch {
                            return null;
                        }
                    })();

                    const visibleTurns = allConversationTurns.filter(el => {
                        const rect = el.getBoundingClientRect();
                        const horizontallyInView = rect.left < viewportWidth && rect.right > 0;
                        const verticallyInView = rect.top < viewportHeight && rect.bottom > 0;
                        if (!horizontallyInView || !verticallyInView) return false;

                        if (composerRect) {
                            if (rect.top >= composerRect.top) {
                                return false;
                            }
                        }

                        return true;
                    });

                    const filteredVisibleTurns = (() => {
                        if (onlySelectAssistant) {
                            return visibleTurns.filter(el =>
                                                       el.querySelector('[data-message-author-role="assistant"]')
                                                      );
                        }
                        if (onlySelectUser) {
                            return visibleTurns.filter(el =>
                                                       el.querySelector('[data-message-author-role="user"]')
                                                      );
                        }
                        return visibleTurns;
                    })();

                    if (filteredVisibleTurns.length === 0) return;

                    filteredVisibleTurns.sort((a, b) => {
                        const ra = a.getBoundingClientRect();
                        const rb = b.getBoundingClientRect();
                        return rb.top - ra.top;
                    });

                    const { lastSelectedIndex } = window.selectAllLowestResponseState;
                    const nextIndex = (lastSelectedIndex + 1) % filteredVisibleTurns.length;
                    const selectedTurn = filteredVisibleTurns[nextIndex];
                    if (!selectedTurn) return;

                    selectAndCopyMessage(selectedTurn);
                    window.selectAllLowestResponseState.lastSelectedIndex = nextIndex;

                    function selectAndCopyMessage(turnElement) {
                        try {
                            const userContainer = turnElement.querySelector('[data-message-author-role="user"]');
                            const isUser = !!userContainer;

                            if (isUser) {
                                if (onlySelectAssistant) return;
                                const userTextElement = userContainer.querySelector('.whitespace-pre-wrap');
                                if (!userTextElement) return;
                                doSelectAndCopy(userTextElement);
                            } else {
                                if (onlySelectUser) return;
                                const assistantContainer = turnElement.querySelector('[data-message-author-role="assistant"]');
                                let textElement = null;
                                if (assistantContainer) {
                                    textElement = assistantContainer.querySelector('.prose') || assistantContainer;
                                } else {
                                    textElement = turnElement.querySelector('.prose') || turnElement;
                                }
                                if (!textElement) return;
                                doSelectAndCopy(textElement);
                            }
                        } catch {
                            // Fail silently
                        }
                    }

                    function doSelectAndCopy(el) {
                        try {
                            const selection = window.getSelection();
                            if (!selection) return;
                            selection.removeAllRanges();

                            const range = document.createRange();
                            range.selectNodeContents(el);
                            selection.addRange(range);

                            if (!disableCopyAfterSelect) {
                                document.execCommand('copy');
                            }
                        } catch {
                            // Fail silently
                        }
                    }

                } catch {
                    // Fail silently
                }
            }, 50);
        }
    });
})();
// Existing script functionalities...
(function() {
    const controlsNavId = 'controls-nav';
    const chatInputId = 'prompt-textarea';

    // Function to handle focusing and manually pasting into the chat input
    function handlePaste(e) {
        const chatInput = document.getElementById(chatInputId);
        if (!chatInput) return;

        // Focus the input if it is not already focused
        if (document.activeElement !== chatInput) {
            chatInput.focus();
        }

        // Use a small delay to ensure focus happens before insertion
        setTimeout(() => {
            // Prevent default paste action to manually handle paste
            e.preventDefault();

            // Obtain the pasted text
            const pastedData = (e.clipboardData || window.clipboardData).getData('text') || '';

            const cursorPosition = chatInput.selectionStart;
            const textBefore = chatInput.value.substring(0, cursorPosition);
            const textAfter = chatInput.value.substring(cursorPosition);

            // Set the new value with pasted data
            chatInput.value = textBefore + pastedData + textAfter;

            // Move the cursor to the end of inserted data
            chatInput.selectionStart = chatInput.selectionEnd = cursorPosition + pastedData.length;

            // Trigger an 'input' event to ensure any form listeners react
            const inputEvent = new Event('input', { bubbles: true, cancelable: true });
            chatInput.dispatchEvent(inputEvent);
        }, 0);
    }

    document.addEventListener('paste', function(e) {
        const activeElement = document.activeElement;

        // If currently focused on a textarea/input that is NOT our chat input, do nothing
        if (
            (activeElement.tagName.toLowerCase() === 'textarea' || activeElement.tagName.toLowerCase() === 'input') &&
            activeElement.id !== chatInputId
        ) {
            return;
        }

        // If currently within #controls-nav, do nothing
        if (activeElement.closest(`#${controlsNavId}`)) {
            return;
        }

        // Otherwise, handle the paste event
        handlePaste(e);
    });
})();

(function() {
    const controlsNavId = 'controls-nav';
    const chatInputId = 'prompt-textarea';

    document.addEventListener('keydown', function(e) {
        const activeElement = document.activeElement;

        // If focused on any other textarea/input besides our chat input, do nothing
        if (
            (activeElement.tagName.toLowerCase() === 'textarea' || activeElement.tagName.toLowerCase() === 'input') &&
            activeElement.id !== chatInputId
        ) {
            return;
        }

        // If currently within #controls-nav, do nothing
        if (activeElement.closest(`#${controlsNavId}`)) {
            return;
        }

        // Check if the pressed key is alphanumeric and no modifier keys are pressed
        const isAlphanumeric = e.key.length === 1 && /[a-zA-Z0-9]/.test(e.key);
        const isModifierKeyPressed = e.altKey || e.ctrlKey || e.metaKey; // metaKey for Cmd on Mac

        if (isAlphanumeric && !isModifierKeyPressed) {
            const chatInput = document.getElementById(chatInputId);
            if (!chatInput) return;

            // If we're not already in our chat input, focus it and add the character
            if (activeElement !== chatInput) {
                e.preventDefault();
                chatInput.focus();
                chatInput.value += e.key;
            }
        }
    });
})();





/*=============================================================
=                                                             =
=  Token counter IIFE                                         =
=                                                             =
=============================================================*/
(function(){
    'use strict';

    // ——— Keys & defaults ———
    const COST_IN_KEY          = 'costInput';
    const COST_OUT_KEY         = 'costOutput';
    const CPT_KEY              = 'charsPerToken';
    let costIn       = parseFloat(localStorage.getItem(COST_IN_KEY))  || 2.50;
    let costOut      = parseFloat(localStorage.getItem(COST_OUT_KEY)) || 10.00;
    let charsPerTok  = parseFloat(localStorage.getItem(CPT_KEY))     || 3.8;
    const OVERHEAD   = 3; // tokens per message overhead

    // ——— Estimator ———
    function estTok(text){
        return Math.ceil((text.trim().length||0)/charsPerTok) + OVERHEAD;
    }

    // ——— UI: badge + refresh button ———
    const badge = document.createElement('span');
    badge.id = 'token-count-badge';
    Object.assign(badge.style, {
        fontSize:'8px', padding:'1px 0 0 6px', borderRadius:'8px',
        background:'transparent', color:'#a9a9a9',
        fontFamily:'monospace', userSelect:'none',
        alignSelf:'center', marginTop:'16px',
        display:'inline-flex', alignItems:'center'
    });

    const refreshBtn = document.createElement('button');
    refreshBtn.textContent = '↻';
    refreshBtn.title   = 'Refresh token count';
    Object.assign(refreshBtn.style, {
        marginLeft:'6px', cursor:'pointer', fontSize:'10px',
        border:'none', background:'transparent',
        color:'#a9a9a9', userSelect:'none',
        fontFamily:'monospace', padding:'0'
    });
    refreshBtn.addEventListener('click', ()=>{
        flash(refreshBtn);
        updateCounts();
    });
    badge.appendChild(refreshBtn);

    function flash(el){
        el.style.transition = 'transform 0.15s';
        el.style.transform  = 'scale(1.4)';
        setTimeout(()=> el.style.transform = 'scale(1)', 150);
    }

    // ——— Inject badge in the “flex row” before mic button ———
    function insertBadge(retries=20){
        const rows = [...document.querySelectorAll('div.flex')];
        const flexRow = rows.find(el =>
                                  el.classList.contains('items-between') &&
                                  el.classList.contains('pb-2')
                                 );
        if(!flexRow){
            if(retries>0) setTimeout(()=> insertBadge(retries-1), 500);
            return null;
        }
        if(!flexRow.querySelector('#token-count-badge')){
            const mic = flexRow.querySelector('button[title="Use microphone"]');
            flexRow.insertBefore(badge, mic);
        }
        return flexRow.parentElement;
    }

    // ——— Role inference ———
    function inferRole(msgEl){
        const wrapper = msgEl.closest('.group, .message');
        if(wrapper?.classList.contains('user'))      return 'user';
        if(wrapper?.classList.contains('assistant')) return 'assistant';
        const all = [...document.querySelectorAll('.message-render')];
        return all.indexOf(msgEl)%2===0 ? 'user' : 'assistant';
    }

    // ——— STORE NUMBERS FOR GSAP ANIMATIONS ———
    const lastValues = {
        inSum: 0,
        outSum: 0,
        total: 0,
        cost: 0
    };

    // ——— Formatting helper ———
    function formatBadgeText({ inSum, outSum, total, cost }) {
        return `${Math.round(inSum)} @ $${costIn}/M | ${Math.round(outSum)} @ $${costOut}/M | ∑ ${Math.round(total)} | $${cost.toFixed(4)}`;
    }

    // ——— updateCounts WITH GSAP ———
    function updateCounts() {
        const msgs = [...document.querySelectorAll('.message-render')];
        if (!msgs.length) {
            lastValues.inSum = 0;
            lastValues.outSum = 0;
            lastValues.total = 0;
            lastValues.cost = 0;
            badge.textContent = '0 | 0 | ∑ 0 | $0.0000';
            badge.appendChild(refreshBtn);
            return;
        }

        const convo = msgs.map(m => ({
            role: inferRole(m),
            t: estTok(m.innerText || '')
        }));
        let inSum = 0, outSum = 0;
        for (let i = 0; i < convo.length; i++) {
            if (convo[i].role === 'user') {
                inSum += convo.slice(0, i + 1).reduce((a, b) => a + b.t, 0);
                const ai = convo.findIndex((c, j) => j > i && c.role === 'assistant');
                if (ai > i) outSum += convo[ai].t;
            }
        }
        const total = inSum + outSum;
        const cost = (inSum / 1e6) * costIn + (outSum / 1e6) * costOut;

        gsap.to(lastValues, {
            inSum, outSum, total, cost,
            duration: 0.7,
            ease: "power1.out",
            onUpdate: () => {
                badge.textContent = formatBadgeText(lastValues);
                badge.appendChild(refreshBtn);
            }
        });
    }

    // ——— Debounce for MutationObserver ———
    let debounceTimer=null;
    function scheduleUpdate(){
        clearTimeout(debounceTimer);
        debounceTimer = setTimeout(updateCounts, 200);
    }

    // ——— Hook send actions for immediate update ———
    function attachSendHooks(){
        const ta = document.querySelector('textarea');
        if(ta && !ta.dataset.tcHooked){
            ta.dataset.tcHooked = 'y';
            ta.addEventListener('keydown', e=>{
                if(e.key==='Enter' && !e.shiftKey && !e.altKey && !e.metaKey){
                    scheduleUpdate();
                }
            });
        }
        const send = document.querySelector('button[type="submit"], button[title="Send"]');
        if(send && !send.dataset.tcHooked){
            send.dataset.tcHooked = 'y';
            send.addEventListener('click', ()=> scheduleUpdate());
        }
    }

    // ——— Initialization ———
    function init(){
        const container = insertBadge();
        if(!container) return;
        // observe only the messages container
        const msgRoot = container.querySelector('.message-render')?.parentElement || container;
        new MutationObserver(scheduleUpdate)
            .observe(msgRoot, { childList:true, subtree:true });
        attachSendHooks();
        // reattach hooks if textarea/send are re-rendered
        new MutationObserver(attachSendHooks)
            .observe(document.body, { childList:true, subtree:true });
        updateCounts();
    }

    // ——— Config shortcut (Alt+U) ———
    document.addEventListener('keydown', e=>{
        if(e.altKey && !e.repeat && e.key.toLowerCase()==='u'){
            e.preventDefault();
            const resp = prompt(
                'Set costs and chars/token:\ninput $/M,output $/M,chars/token',
                `${costIn},${costOut},${charsPerTok}`
            );
            if(!resp) return;
            const [ci,co,cpt] = resp.split(',').map(Number);
            if([ci,co,cpt].every(v=>isFinite(v))){
                costIn = ci; costOut = co; charsPerTok = cpt;
                localStorage.setItem(COST_IN_KEY,ci);
                localStorage.setItem(COST_OUT_KEY,co);
                localStorage.setItem(CPT_KEY,cpt);
                updateCounts();
            } else alert('Invalid numbers');
        }
    });

    // delay to let page render
    setTimeout(init, 1000);

})();

// // // // // //
// Convert <br> in tables displaying as literal <br> to line breaks
// // // // // //
(function () {
    'use strict';

    const BR_ENTITY_REGEX = /&lt;br\s*\/?&gt;/gi;

    function fixBrsInMarkdown() {
        document.querySelectorAll('div.markdown').forEach(container => {
            container.querySelectorAll('td, th, p, li, div').forEach(el => {
                if (el.innerHTML.includes('&lt;br')) {
                    el.innerHTML = el.innerHTML.replace(BR_ENTITY_REGEX, '<br>');
                }
            });
        });
    }

    // Run once in case content is already loaded
    fixBrsInMarkdown();

    // Watch for content changes
    const observer = new MutationObserver(() => fixBrsInMarkdown());

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


// Alt+e   →   toggle collapse expand chat
(function() {
    document.addEventListener('keydown', function(e) {
        if (e.altKey && e.key.toLowerCase() === 'e') {
            e.preventDefault();
            const collapseBtn = document.querySelector('button[aria-label="Collapse Chat"]');
            if (collapseBtn) {
                collapseBtn.click();
                return;
            }
            const expandBtn = document.querySelector('button[aria-label="Expand Chat"]');
            if (expandBtn) expandBtn.click();
        }
    });
})();


// alt+1 to alt+9 to select presets
// alt+1 to alt+9 to select presets
(() => {
    const synthClick = el => {
        if (!el) return;
        el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
        el.focus?.();
    };

    function isMenuOpen() {
        // This assumes the menu creates at least one .[role="listbox"] when open, adjust selector if needed
        // Also checks for at least one preset option in the DOM
        return !!document.querySelector('div[role="option"][data-testid^="preset-item"]');
    }

    function handleAltDigit(ev) {
        if (!ev.altKey || !/^Digit[1-9]$/.test(ev.code)) return;
        ev.preventDefault();
        ev.stopPropagation();

        const idx = +ev.code.slice(-1) - 1;
        const btn = document.getElementById('presets-button');
        if (!btn) return console.warn('[Preset-helper] #presets-button not found');

        // Only click the button if the menu is not already open
        const alreadyOpen = isMenuOpen();

        if (!alreadyOpen) btn.click();

        // If menu was *just* opened, may need small delay for items to render
        const delay = alreadyOpen ? 0 : 500;
        setTimeout(() => {
            const items = Array.from(
                document.querySelectorAll('div[role="option"][data-testid^="preset-item"]')
            );
            if (items[idx]) synthClick(items[idx]);
            else console.warn('[Preset-helper] Preset item not available at index', idx);
        }, delay);
    }

    window.addEventListener('keydown', handleAltDigit, true);
})();



// Label the presets

(() => {
    /* ——— simple style for the tiny label ——— */
    const style = document.createElement('style');
    style.textContent = `
    .alt-hint {
      font-size: 12px;          /* small text */
      opacity: .5;              /* 50 % opacity  */
      margin-left: 4px;         /* a little gap  */
      pointer-events: none;     /* never blocks clicks */
      user-select: none;
    }`;
    document.head.appendChild(style);

    const ITEM_SELECTOR = 'div[role="option"][data-testid^="preset-item"]';
    const MAX_DIGITS = 9; // Alt+1 … Alt+9

    /** add the hint to each item (if not already present) */
    const addHints = () => {
        [...document.querySelectorAll(ITEM_SELECTOR)]
            .slice(0, MAX_DIGITS)
            .forEach((el, i) => {
            if (el.querySelector('.alt-hint')) return;      // only once
            const span = document.createElement('span');
            span.className = 'alt-hint';
            span.textContent = `Alt+${i + 1}`;
            el.appendChild(span);
        });
    };

    /* run once right now (in case the menu is already open) */
    addHints();

    /* keep watching for future openings of the menu */
    const mo = new MutationObserver(addHints);
    mo.observe(document.body, { childList: true, subtree: true });
})();



// alt+w   →   Open the preset menu

(() => {
    'use strict';

    window.addEventListener('keydown', handleAltP, true);

    const openPresetMenu = () => {
        const btn = document.getElementById('presets-button');
        if (!btn) {
            console.log('[Preset-helper] couldn’t find #presets-button');
            return false;
        }
        btn.click();
        return true;
    };

    function handleAltP(e) {
        if (e.altKey && e.code === 'KeyW') {
            e.preventDefault();
            e.stopPropagation();
            openPresetMenu();
        }
    }
})();



// Click right sidebar toggle with alt+g for game settings
(function() {
    document.addEventListener('keydown', function(e) {
        // Only proceed if ALT+G is pressed
        if (!e.altKey || e.key.toLowerCase() !== 'g') return;

        const nav = document.querySelector('nav[aria-label="Controls"][role="navigation"]');
        const width = nav ? nav.getBoundingClientRect().width : 0;

        if (width > 100) {
            // Panel is open: click "Hide Panel" button
            const hideBtn = [...document.querySelectorAll('button')].find(
                b => b.textContent.trim().toLowerCase().includes('hide panel')
            );
            if (hideBtn) hideBtn.click();
        } else {
            // Panel is closed: click toggle-right-nav, then wait for "Parameters" button to appear
            const toggleBtn = document.getElementById('toggle-right-nav');
            if (toggleBtn) {
                toggleBtn.click();

                const maxRetryTime = 5000; // how long to wait, in ms
                let elapsed = 0;
                const interval = 100;

                const intervalId = setInterval(() => {
                    elapsed += interval;
                    // Find a button containing the text "Parameters"
                    const paramsBtn = [...document.querySelectorAll('button')]
                    .find(b => b.textContent.trim().toLowerCase().includes('parameters'));

                    if (paramsBtn) {
                        clearInterval(intervalId);
                        paramsBtn.click();
                    } else if (elapsed >= maxRetryTime) {
                        clearInterval(intervalId);
                        console.warn("Parameters button not found within time limit.");
                    }
                }, interval);
            }
        }
    });
})();




// Make the preset dialogue 100% viewport height
// Make the preset dialogue 100% viewport height


(function() {
    const style = document.createElement('style');
    style.textContent = `
      [data-side="bottom"][data-align="center"][data-state="open"] {
        max-height: 100% !important;
        height: 90vh !important;
        overflow: auto;
      }
.preset-name {
  font-weight: bold;
  color: #f9cc87; /* electric orange */
  font-size: 115% !important;
}
.preset-number {
  color: #bdccff; /* baby/light blue */
  margin-right: 6px;
}

    `;
    document.head.appendChild(style);

    function highlightPresetNames(container) {
        const textDivs = container.querySelectorAll('div.text-xs');
        let counter = 1;

        textDivs.forEach(div => {
            const textNode = Array.from(div.childNodes).find(node => node.nodeType === Node.TEXT_NODE && node.nodeValue.includes(':'));
            if (textNode) {
                const match = textNode.nodeValue.match(/^(.*?):\s*(.*)$/s);
                if (match) {
                    const beforeColon = match[1];
                    const afterColon = match[2];
                    div.innerHTML = `
                      <span class="preset-name">
                        <span class="preset-number">${counter}.</span>${beforeColon}&nbsp;
                      </span>(${afterColon.trim()})
                    `.trim();
                    counter++;
                }
            }
        });
    }

    function runHighlight() {
        const container = document.querySelector('[data-side="bottom"][data-align="center"][data-state="open"]');
        if (container) {
            highlightPresetNames(container);
        }
    }

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

    const observer = new MutationObserver(() => runHighlight());
    observer.observe(document.body, { childList: true, subtree: true });
})();



// Insert stop dictation button
(async function () {
    // 1. Inject Google Material Icons (if not already present)
    if (!document.querySelector('link[href*="fonts.googleapis.com/icon?family=Material+Icons"]')) {
        const link = document.createElement('link');
        link.href = 'https://fonts.googleapis.com/icon?family=Material+Icons';
        link.rel = 'stylesheet';
        document.head.appendChild(link);
    }

    // 2. Dynamically import SpeechRecognition
    let SpeechRecognition;
    try {
        SpeechRecognition = (await import('https://cdn.jsdelivr.net/npm/[email protected]/dist/index.umd.min.js')).default;
    } catch (e) {
        console.error('Failed to import SpeechRecognition:', e);
        return;
    }

    // 3. Poll DOM for the microphone button
    const interval = setInterval(() => {
        const micBtn = document.querySelector('#audio-recorder');
        if (!micBtn || document.querySelector('#stop-dictation')) return;

        // 4. Create Stop Dictation button
        const stopBtn = document.createElement('button');
        stopBtn.id = 'stop-dictation';
        stopBtn.title = 'Stop Dictation';
        stopBtn.setAttribute('aria-label', 'Stop Dictation');
        stopBtn.style.marginLeft = '6px';
        stopBtn.className = 'cursor-pointer flex size-9 items-center justify-center rounded-full p-1 transition-colors hover:bg-surface-hover';

        stopBtn.innerHTML = `<span class="material-icons" style="font-size: 24px; color: red;">stop_circle</span>`;

        // 5. Insert after mic button
        micBtn.parentNode.insertBefore(stopBtn, micBtn.nextSibling);

        // 6. Attach event
        stopBtn.addEventListener('click', async () => {
            try {
                await SpeechRecognition.stopListening();
                console.log('Speech recognition manually stopped.');
            } catch (err) {
                console.error('Failed to stop speech recognition:', err);
            }
        });

        clearInterval(interval);
    }, 500);
})();



// This script injects a CSS rule into the current page
(function() {
    const style = document.createElement('style');
    style.textContent = `
/* 2. make sure every normal text line looks reasonable */
.markdown.prose.message-content,
.markdown.prose.message-content p,
.markdown.prose.message-content li {
  line-height: 1.45 !important;   /* ≈ 23px on a 16px font */
}

/* hide the share button */
#export-menu-button {
display:none;
    }


  `;
    document.head.appendChild(style);
})();



// alt+r toggles hiding the top bar in narrow mode
(() => {
    console.log("Content script loaded: debug version");

    // The CSS selector suspected to match the target
    const SELECTOR = ".bg-token-main-surface-primary.sticky.top-0.z-10.flex.min-h-\\[40px\\].items-center.justify-center.bg-white.pl-1.dark\\:bg-gray-800.dark\\:text-white.md\\:hidden";

    function debugQuerySelector() {
        const foundElem = document.querySelector(SELECTOR);
        console.log("Debug: querySelector returned:", foundElem);
        return foundElem;
    }

    // Immediately check if the element is found at load
    debugQuerySelector();

    const STYLE_ID = "ext-hide-bg-token-main-surface-primary";
    const cssRule = `${SELECTOR} { display: none !important; }`;

    function addStyle() {
        if (!document.getElementById(STYLE_ID)) {
            const style = document.createElement("style");
            style.id = STYLE_ID;
            style.textContent = cssRule;
            document.head.appendChild(style);
        }
    }

    function removeStyle() {
        const existing = document.getElementById(STYLE_ID);
        if (existing) {
            existing.remove();
        }
    }

    function isHidden() {
        return !!document.getElementById(STYLE_ID);
    }

    function toggleStyle() {
        console.log("Toggle requested...");
        if (isHidden()) {
            removeStyle();
            console.log("Removed style tag, showing element again.");
        } else {
            addStyle();
            console.log("Added style tag, hiding element.");
        }
    }

    // Hide the element by default at page load
    addStyle();

    document.addEventListener("keydown", (e) => {
        // Log all key presses for debugging
        console.log(`Key pressed: ${e.key}, altKey = ${e.altKey}`);

        // ALT+R, ignoring if user is typing in an input/textarea
        if (
            e.altKey &&
            e.key.toLowerCase() === "r" &&
            !["INPUT", "TEXTAREA"].includes(document.activeElement.tagName)
        ) {
            e.preventDefault();
            toggleStyle();

            // Check again if the element is found after toggling
            debugQuerySelector();
        }
    });
})();



// Change multi conversation split icon to an intuitive icon
(() => {
    // 1. Inject Material Symbols stylesheet if not already present
    if (!document.querySelector('link[href*="fonts.googleapis.com"][href*="Material+Symbols"]')) {
        const link = document.createElement('link');
        link.rel = 'stylesheet';
        link.href = 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200&icon_names=alt_route,star';
        document.head.appendChild(link);
    }

    // 2. Replace SVG in #add-multi-conversation-button with "alt_route" icon
    function replaceAddButtonIcon() {
        const buttonDiv = document.querySelector('#add-multi-conversation-button');
        if (buttonDiv && !buttonDiv.querySelector('.material-symbols-outlined')) {
            const svg = buttonDiv.querySelector('svg');
            if (svg) {
                const iconSpan = document.createElement('span');
                iconSpan.className = 'material-symbols-outlined';
                iconSpan.textContent = 'alt_route';
                iconSpan.style.fontSize = '16px';
                iconSpan.style.width = '16px';
                iconSpan.style.height = '16px';
                iconSpan.style.display = 'inline-flex';
                iconSpan.style.alignItems = 'center';
                iconSpan.style.justifyContent = 'center';
                svg.replaceWith(iconSpan);
            }
        }
    }

    // 3. Replace all .lucide-book-copy[aria-label="Preset Icon"] SVGs with "star" icon
    function replaceBookCopyIcons() {
        const svgs = document.querySelectorAll('svg.lucide-book-copy[aria-label="Preset Icon"]');
        svgs.forEach(svg => {
            if (svg.dataset.replacedWithMaterialSymbol) return;
            const iconSpan = document.createElement('span');
            iconSpan.className = 'material-symbols-outlined';
            iconSpan.textContent = 'star';
            iconSpan.style.fontSize = '16px';
            iconSpan.style.width = '16px';
            iconSpan.style.height = '16px';
            iconSpan.style.display = 'inline-flex';
            iconSpan.style.alignItems = 'center';
            iconSpan.style.justifyContent = 'center';
            svg.dataset.replacedWithMaterialSymbol = "true";
            svg.replaceWith(iconSpan);
        });
    }

    // 4. Composite function
    function replaceIcons() {
        replaceAddButtonIcon();
        replaceBookCopyIcons();
    }

    // 5. Debounce utility
    function debounce(fn, delay) {
        let timeout;
        return (...args) => {
            clearTimeout(timeout);
            timeout = setTimeout(() => fn(...args), delay);
        };
    }
    const debouncedReplaceIcons = debounce(replaceIcons, 100);

    // 6. MutationObserver with debounce, observing entire body
    const observer = new MutationObserver(debouncedReplaceIcons);
    observer.observe(document.body, { subtree: true, childList: true });

    // 7. Initial run on DOMContentLoaded or immediately
    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", replaceIcons);
    } else {
        replaceIcons();
    }
})();



// trigger stop with control+backspace and send with control+enter and control+; clicks stop then regenerate (or just regenerate if stop isn't available).
(function () {
  // Utility: is element in viewport
  function isElementInViewport(el) {
    const rect = el.getBoundingClientRect();
    return (
      rect.width > 0 &&
      rect.height > 0 &&
      rect.bottom > 0 &&
      rect.top < window.innerHeight &&
      rect.left < window.innerWidth &&
      rect.right > 0
    );
  }

  // Utility: find lowest visible Regenerate button
  function getLowestVisibleRegenerateBtn() {
    const regenBtns = Array.from(document.querySelectorAll('button[title="Regenerate"]:not([disabled])'));
    const visibleBtns = regenBtns.filter(isElementInViewport);
    if (visibleBtns.length === 0) return null;
    // Find the one with the largest .getBoundingClientRect().top
    return visibleBtns.reduce((lowest, btn) => {
      const currTop = btn.getBoundingClientRect().top;
      const lowestTop = lowest.getBoundingClientRect().top;
      return currTop > lowestTop ? btn : lowest;
    }, visibleBtns[0]);
  }

  document.addEventListener('keydown', function (e) {
    // Allow shortcuts even in input/textarea/CEs
    // Ctrl+Backspace - STOP
    if (e.ctrlKey && e.key === 'Backspace') {
      e.preventDefault();
      const stopBtn = document.querySelector('button[aria-label="Stop generating"]:not([disabled])');
      if (stopBtn) stopBtn.click();
      return;
    }

    // Ctrl+Enter - SEND
    if (e.ctrlKey && (e.key === 'Enter' || e.keyCode === 13)) {
      e.preventDefault();
      const sendBtn = document.querySelector(
        'button[aria-label="Send message"]:not([disabled]), #send-button:not([disabled]), button[data-testid="send-button"]:not([disabled])'
      );
      if (sendBtn) sendBtn.click();
      return;
    }

    // Ctrl+; - STOP, then REGENERATE
    if (e.ctrlKey && (e.key === ';' || e.code === 'Semicolon')) {
      e.preventDefault();
      const stopBtn = document.querySelector('button[aria-label="Stop generating"]:not([disabled])');
      if (stopBtn) stopBtn.click();

      setTimeout(function () {
        const lowestRegenBtn = getLowestVisibleRegenerateBtn();
        if (lowestRegenBtn) lowestRegenBtn.click();
      }, 750);
      return;
    }
  });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址