Gemini Code Block Scroller (v2.0)

Gemini 代码块滚动增强:标题居中、状态跟随、自动历史补全、双击展开。

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Gemini Code Block Scroller (v2.0)
// @namespace    http://tampermonkey.net/
// @version      2.0.0
// @description  Gemini 代码块滚动增强:标题居中、状态跟随、自动历史补全、双击展开。
// @author       Gemini User
// @match        https://gemini.google.com/*
// @grant        GM_addStyle
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // ================= 配置区域 =================
    const CONFIG = {
        maxHeight: '300px',       // 限制高度
        transitionTime: '0.2s',   // 动画速度
        statusText: {
            generating: '⏳ 生成中...',
            done: '✅ 代码生成完毕'
        }
    };

    // 1. 注入样式
    GM_addStyle(`
        /* 核心滚动样式 */
        .code-block .formatted-code-block-internal-container pre {
            max-height: ${CONFIG.maxHeight} !important;
            overflow-y: auto !important;
            display: block !important;
            cursor: zoom-in !important;
            transition: max-height ${CONFIG.transitionTime} ease-out;
            padding-bottom: 20px !important;
            border-bottom: 2px solid rgba(138, 180, 248, 0.1);
        }
        
        .code-block .formatted-code-block-internal-container pre.gm-expanded {
            max-height: none !important;
            cursor: zoom-out !important;
        }

        /* === 布局核心样式 === */
        
        /* 1. 状态标签样式 */
        .gm-status-badge {
            font-size: 12px;
            padding: 2px 8px;
            border-radius: 12px;
            
            /* 关键布局:把右边的按钮组狠狠推到最右边 */
            margin-right: auto !important; 
            
            /* 和左边的语言标签保持距离 */
            margin-left: 10px !important; 
            
            font-family: "Google Sans", Roboto, sans-serif;
            font-weight: 500;
            z-index: 999;
            display: inline-flex;
            align-items: center;
            white-space: nowrap;
            height: 24px;
        }
        
        /* 2. 语言标签样式 (JS动态添加此类名) */
        /* 作用:把左边的红绿灯/空白狠狠推到最左边 */
        .gm-lang-label-centered {
            margin-left: auto !important;
        }

        .gm-status-badge.generating {
            background-color: rgba(253, 214, 99, 0.15);
            color: #fdd663;
            border: 1px solid rgba(253, 214, 99, 0.3);
            animation: pulse 1.5s infinite;
        }
        
        .gm-status-badge.done {
            background-color: rgba(129, 201, 149, 0.15);
            color: #81c995;
            border: 1px solid rgba(129, 201, 149, 0.3);
        }

        @keyframes pulse {
            0% { opacity: 0.6; }
            50% { opacity: 1; }
            100% { opacity: 0.6; }
        }
    `);

    // 2. 双击交互
    document.addEventListener('dblclick', function(e) {
        const preBlock = e.target.closest('.formatted-code-block-internal-container pre');
        if (preBlock) {
            e.preventDefault();
            window.getSelection()?.removeAllRanges();
            preBlock.classList.toggle('gm-expanded');
        }
    });

    // 3. 智能检测与布局修正
    setInterval(() => {
        fixAllBlocks();
    }, 500);

    function checkIsGenerating() {
        const buttons = Array.from(document.querySelectorAll('button[aria-label]'));
        const stopBtn = buttons.find(btn => {
            const label = btn.getAttribute('aria-label');
            const hasStop = label.includes('Stop') || label.includes('停止');
            const isMedia = label.includes('朗读') || label.includes('收听') || label.includes('播放') || label.includes('Read') || label.includes('Listen');
            return hasStop && !isMedia;
        });
        return !!stopBtn;
    }

    function fixAllBlocks() {
        const allBlocks = document.querySelectorAll('.code-block');
        if (allBlocks.length === 0) return;

        const isGenerating = checkIsGenerating();

        allBlocks.forEach((block, index) => {
            const isLast = index === allBlocks.length - 1;
            const status = (isGenerating && isLast) ? 'generating' : 'done';
            
            ensureBadgeAndLayout(block, status);
        });
    }

    function ensureBadgeAndLayout(block, statusType) {
        let header = block.querySelector('.code-block-decoration');
        if (!header) header = block.querySelector('div:first-child');
        if (!header) return;

        // === 步骤1:处理语言标签居中 ===
        // 查找语言标签 (通常是 span)
        const langSpan = header.querySelector('span');
        if (langSpan && !langSpan.classList.contains('gm-lang-label-centered')) {
            langSpan.classList.add('gm-lang-label-centered');
        }

        // === 步骤2:处理状态徽章 ===
        let badge = header.querySelector('.gm-status-badge');
        const targetText = statusType === 'generating' ? CONFIG.statusText.generating : CONFIG.statusText.done;

        if (!badge) {
            badge = document.createElement('span');
            badge.className = 'gm-status-badge ' + statusType;
            badge.textContent = targetText;
            
            // 插入逻辑:紧跟在语言标签后面
            if (langSpan) {
                // 插在 span 后面,这样它们就形成了一个中间的组
                // header.insertBefore(badge, langSpan.nextSibling);
                // 为了保险,如果 span 后面有东西,插在它前面;如果没东西,appendChild
                if (langSpan.nextSibling) {
                    header.insertBefore(badge, langSpan.nextSibling);
                } else {
                    header.appendChild(badge);
                }
            } else {
                // 找不到 span?那只能插在最前面凑合一下
                header.appendChild(badge);
            }
        } 
        else if (badge.textContent !== targetText || !badge.classList.contains(statusType)) {
            badge.className = 'gm-status-badge ' + statusType;
            badge.textContent = targetText;
        }
    }

    console.log('Gemini Scroller v2.4: 居中布局版已启动');
})();