gemini 优化油猴脚本(程序开发必备)

gemini 一直以来 canvas 有 2 个问题导致编程变成很不方便

一个项目有很多分文件,但是 gemini 每次找分文件只有 2 种方法

1.向上滑动寻找(极其不方便,尤其是上下文对话很长的时候)

2.点击右上角的此对话中的文件(没有更新顺序,不如 claude 的顺序层次分明)

每次打开很长的对话回答都会显示部分对话,向上滑动才会动态加载,但还是加载不全,导致那个此对话中的文件也没加载全

针对这 2 个问题,做出来一下油猴脚本


🚀 Gemini 优化助手 (Gemini Optimizer) 使用说明

简介

Gemini 优化助手 是一款专为 Google Gemini 网页版设计的油猴脚本。它旨在解决原版界面中文件列表查看不便、历史记录加载繁琐等痛点,提供更流畅、更现代化的交互体验。

✨ 核心功能

1. 📂 侧边栏文件列表优化

  • 悬停即现:无需点击,只需将鼠标悬停在右上角的“文件”图标(或侧边栏区域),列表即会自动展开。
  • 智能排序:自动读取文件的时间戳,将列表按时间倒序排列(最新的文件在最上面),找代码更方便。
  • UI 美化:
    • 将侧边栏改为纯白/深色适配的圆角悬浮卡片风格。
    • 去除了原生按钮点击后难看的“深色圆圈”选中状态。
    • 侧边栏不再挤占右侧空间,而是悬浮在顶层,视觉更清爽。

2. 📥 历史记录一键加载(独家功能)

  • 独立悬浮按钮:屏幕右侧新增一个圆形的“置顶/加载”按钮。
  • 一键“爬楼”:点击按钮,脚本会自动模拟滚动操作,一直向上翻阅直到加载完所有历史对话。
  • 静默加载体验:
    • 点击后会出现一个全屏遮罩(带动画),提示“正在挖掘历史记录”。
    • 加载过程中页面不会乱跳,脚本在后台默默工作。
    • 无缝归位:加载完成后,遮罩消失,页面会自动跳转回你点击按钮前看到的位置,实现无缝衔接。

3. 🖱️ 悬浮按钮自由拖拽

  • 随心所欲:觉得按钮挡视线?按住它,拖到屏幕任意位置即可。
  • 位置记忆:脚本会自动记住你放置按钮的位置,刷新页面或下次打开时,它依然在那里。

4. ⚙️ 个性化设置

  • 开关控制:如果你暂时不需要“自动加载”功能,可以在油猴菜单中一键关闭悬浮按钮。

📖 操作指南

如何安装

  1. 确保浏览器已安装 Tampermonkey (篡改猴) 扩展。
  2. 新建一个脚本,将代码完整粘贴进去并保存(或通过链接安装)。
  3. 刷新 Gemini 页面即可生效。

如何查看文件列表

  1. 将鼠标移动到页面右上角的 📁 文件图标 上。
  2. 侧边栏会自动悬浮显示。
  3. 移开鼠标(且不悬停在侧边栏上)约 0.3 秒后,侧边栏自动消失。

如何加载全部历史记录

  1. 找到屏幕上的 圆形悬浮按钮(默认在右上方)。
  2. 点击 该按钮。
  3. 等待遮罩层的进度提示(通常几秒钟,取决于对话长度)。
  4. 看到“✅ 历史记录加载完毕”提示后,即可尽情浏览旧对话。

如何移动悬浮按钮

  1. 按住 悬浮按钮不放。
  2. 拖动到你喜欢的位置。
  3. 松开鼠标,位置自动保存。

如何隐藏/显示悬浮按钮

  1. 点击浏览器地址栏右侧的 Tampermonkey 图标。
  2. 在弹出的菜单中,找到 gemini 优化 下方的选项。
  3. 点击 🚫 隐藏悬浮加载按钮 即可隐藏。
  4. 如需恢复,再次点击该菜单(此时显示为 🔘 显示悬浮加载按钮)。
// ==UserScript==
// @name         gemini优化
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  优化 Gemini 文件列表体验:悬停自动打开(白底美化),按时间倒序。屏幕右侧新增独立悬浮按钮(支持拖拽+记忆位置+开关设置),智能侦测滚动区域,支持“静默加载全部历史记录”,加载过程显示遮罩,完成后无缝归位。
// @author       You
// @match        https://gemini.google.com/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @run-at       document-idle
// @noframes
// ==/UserScript==

(function() {
    'use strict';

    // 配置键名
    const CONFIG_KEY_FLOAT_BTN = 'gemini_float_btn_enabled';
    const CONFIG_KEY_BTN_POS = 'gemini_optimizer_btn_pos';

    // =========================================================================
    // 1. CSS 样式注入
    // =========================================================================
    GM_addStyle(`
        /* 侧边栏样式 */
        context-sidebar {
            position: fixed !important;
            top: 64px !important;
            right: 80px !important;
            height: auto !important;
            max-height: 80vh !important;
            width: 340px !important;
            z-index: 10000 !important;
            background-color: #ffffff !important;
            border: 1px solid #e0e0e0 !important;
            border-radius: 16px !important;
            box-shadow: 0 10px 30px rgba(0,0,0,0.15) !important;
            display: none;
            overflow: hidden !important;
            transition: opacity 0.2s ease-in-out;
        }
        context-sidebar:hover { display: block !important; opacity: 1 !important; }
        context-sidebar .studio-sidebar-container { height: auto !important; max-height: 75vh !important; overflow-y: auto !important; padding: 8px 0 !important; }

        context-sidebar .header button[aria-label="关闭边栏"] { display: none !important; }

        context-sidebar .gds-title-l, context-sidebar .immersive-title, context-sidebar .sources-list-row-title { color: #1f1f1f !important; font-weight: 600 !important; }
        context-sidebar .immersive-subtitle, context-sidebar .sources-list-row-attribution { color: #5e5e5e !important; }
        context-sidebar mat-icon { color: #444746 !important; }
        context-sidebar sidebar-immersive-chip .container:hover, context-sidebar .clickable:hover { background-color: #f5f5f5 !important; border-radius: 8px; }

        button[data-test-id="studio-sidebar-button"].clicked { background-color: transparent !important; box-shadow: none !important; }
        button[data-test-id="studio-sidebar-button"].clicked .mat-mdc-button-persistent-ripple { opacity: 0 !important; display: none !important; }
        button[data-test-id="studio-sidebar-button"].clicked mat-icon { color: inherit !important; }

        /* Toast & Loading Styles */
        #gemini-load-toast {
            position: fixed;
            top: 80px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(30, 31, 32, 0.95);
            color: white;
            padding: 10px 20px;
            border-radius: 24px;
            z-index: 20001;
            font-size: 14px;
            pointer-events: none;
            opacity: 0;
            transition: opacity 0.3s;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            text-align: center;
            font-weight: 500;
        }
        #gemini-load-toast.show { opacity: 1; }

        #gemini-float-load-btn {
            position: fixed;
            width: 44px;
            height: 44px;
            border-radius: 50%;
            background-color: white;
            border: 1px solid #e0e0e0;
            box-shadow: 0 4px 12px rgba(0,0,0,0.1);
            z-index: 20000;
            cursor: grab;
            display: flex;
            align-items: center;
            justify-content: center;
            opacity: 0.6;
            transition: opacity 0.2s, transform 0.1s, box-shadow 0.2s;
            touch-action: none;
            user-select: none;
        }
        #gemini-float-load-btn:active {
            cursor: grabbing;
            transform: scale(0.95);
        }
        #gemini-float-load-btn:hover {
            opacity: 1;
            background-color: #f8f9fa;
            border-color: #dadce0;
            box-shadow: 0 6px 16px rgba(0,0,0,0.15);
        }
        #gemini-float-load-btn mat-icon { color: #5f6368; font-size: 24px; width: 24px; height: 24px; pointer-events: none; }

        @keyframes spin-anim { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
        .spin-anim { animation: spin-anim 1s linear infinite; }
    `);

    // =========================================================================
    // 2. 工具函数
    // =========================================================================
    function parseChineseTime(timeStr) {
        if (!timeStr) return 0;
        try {
            const year = new Date().getFullYear();
            let cleanStr = timeStr.replace(/月/g, '/').replace(/,/g, '').replace(/下午/g, 'PM').replace(/上午/g, 'AM').trim();
            const timestamp = Date.parse(`${year}/${cleanStr}`);
            return isNaN(timestamp) ? 0 : timestamp;
        } catch (e) { return 0; }
    }

    function showToast(text, duration = 0) {
        let toast = document.getElementById('gemini-load-toast');
        if (!toast) {
            toast = document.createElement('div');
            toast.id = 'gemini-load-toast';
            document.body.appendChild(toast);
        }
        toast.textContent = text;
        toast.classList.add('show');

        if (duration > 0) {
            setTimeout(() => { toast.classList.remove('show'); }, duration);
        }
    }

    // =========================================================================
    // 3. 核心算法:智能侦测滚动容器
    // =========================================================================
    function findChatScroller() {
        const candidates = document.querySelectorAll('main, infinite-scroller, .scrollable-content, .message-content');
        for (const el of candidates) {
            if (el.scrollHeight > el.clientHeight && el.clientHeight > 100) {
                return el;
            }
        }

        const allDivs = document.querySelectorAll('div');
        let maxArea = 0;
        let bestTarget = null;

        for (const el of allDivs) {
            const style = window.getComputedStyle(el);
            const isScrollable = (style.overflowY === 'auto' || style.overflowY === 'scroll') || (el.scrollHeight > el.clientHeight + 50);

            if (isScrollable) {
                const rect = el.getBoundingClientRect();
                if (rect.height > 200 && rect.width > 300) {
                    const area = rect.width * rect.height;
                    if (area > maxArea) {
                        maxArea = area;
                        bestTarget = el;
                    }
                }
            }
        }
        return bestTarget || document.scrollingElement || document.body;
    }

    // =========================================================================
    // 4. 核心功能 A:文件列表
    // =========================================================================
    let hideTimer = null;
    let isSorting = false;

    function sortFiles(containerContext) {
        if (isSorting) return;
        const root = containerContext || document.querySelector('context-sidebar');
        if (!root) return;
        const listContainer = root.querySelector('.source-container');
        if (!listContainer) return;
        const items = Array.from(listContainer.querySelectorAll('sidebar-immersive-chip'));
        if (items.length <= 1) return;

        isSorting = true;
        items.sort((a, b) => {
            const timeA = parseChineseTime(a.querySelector('.immersive-subtitle')?.textContent?.trim());
            const timeB = parseChineseTime(b.querySelector('.immersive-subtitle')?.textContent?.trim());
            return timeB - timeA;
        });

        const fragment = document.createDocumentFragment();
        items.forEach(item => fragment.appendChild(item));
        listContainer.appendChild(fragment);
        setTimeout(() => { isSorting = false; }, 50);
    }

    function showSidebar() {
        const sidebar = document.querySelector('context-sidebar');
        const button = document.querySelector('button[data-test-id="studio-sidebar-button"]');
        if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }

        if (sidebar) {
            sidebar.style.display = 'block';
            sortFiles(sidebar);
            bindSidebarEvents(sidebar);
        } else if (button) {
            button.click();
            let checkCount = 0;
            const waitSidebar = setInterval(() => {
                const newSidebar = document.querySelector('context-sidebar');
                checkCount++;
                if (newSidebar) {
                    clearInterval(waitSidebar);
                    newSidebar.style.display = 'block';
                    bindSidebarEvents(newSidebar);
                    sortFiles(newSidebar);
                } else if (checkCount > 20) clearInterval(waitSidebar);
            }, 100);
        }
    }

    function hideSidebarDelayed() {
        hideTimer = setTimeout(() => {
            const sidebar = document.querySelector('context-sidebar');
            if (sidebar && !sidebar.matches(':hover')) {
                sidebar.style.display = 'none';
            }
        }, 300);
    }

    function bindSidebarEvents(sidebar) {
        if (sidebar.dataset.geminiEventsBound) return;
        sidebar.addEventListener('mouseenter', () => { if (hideTimer) clearTimeout(hideTimer); });
        sidebar.addEventListener('mouseleave', hideSidebarDelayed);
        const observer = new MutationObserver(() => { if (!isSorting) setTimeout(() => sortFiles(sidebar), 200); });
        observer.observe(sidebar, { childList: true, subtree: true });
        sidebar.dataset.geminiEventsBound = 'true';
    }

    // =========================================================================
    // 5. 核心功能 B:静默加载历史
    // =========================================================================

    window._geminiStopLoading = false;

    function createLoadingOverlay() {
        if (document.getElementById('gemini-loading-overlay')) return;

        const overlay = document.createElement('div');
        overlay.id = 'gemini-loading-overlay';

        Object.assign(overlay.style, {
            position: 'fixed',
            top: '0',
            left: '0',
            width: '100vw',
            height: '100vh',
            backgroundColor: 'rgba(255, 255, 255, 0.95)',
            zIndex: '99999',
            display: 'flex',
            flexDirection: 'column',
            alignItems: 'center',
            justifyContent: 'center',
            color: '#333'
        });

        if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
            overlay.style.backgroundColor = 'rgba(30, 31, 32, 0.95)';
            overlay.style.color = '#fff';
        }

        const icon = document.createElement('mat-icon');
        icon.className = 'mat-icon notranslate google-symbols mat-ligature-font spin-anim';
        icon.style.cssText = 'font-size: 48px; width: 48px; height: 48px; margin-bottom: 20px; color: #1a73e8;';
        icon.textContent = 'sync';

        const text = document.createElement('div');
        text.id = 'gemini-loading-text';
        text.style.cssText = 'font-size: 18px; font-weight: 500; margin-bottom: 8px;';
        text.textContent = '正在挖掘历史记录...';

        const subtext = document.createElement('div');
        subtext.style.cssText = 'font-size: 14px; opacity: 0.7; margin-bottom: 30px;';
        subtext.textContent = '保持页面静止,完成后将自动归位';

        const stopBtn = document.createElement('button');
        stopBtn.textContent = '停止加载';
        stopBtn.style.cssText = 'padding: 8px 24px; border-radius: 18px; border: 1px solid rgba(128,128,128,0.3); background: transparent; color: inherit; cursor: pointer; font-size: 14px;';
        stopBtn.onmouseover = () => stopBtn.style.background = 'rgba(128,128,128,0.1)';
        stopBtn.onmouseout = () => stopBtn.style.background = 'transparent';
        stopBtn.onclick = () => { window._geminiStopLoading = true; };

        overlay.appendChild(icon);
        overlay.appendChild(text);
        overlay.appendChild(subtext);
        overlay.appendChild(stopBtn);

        document.body.appendChild(overlay);
    }

    function removeLoadingOverlay() {
        const overlay = document.getElementById('gemini-loading-overlay');
        if (overlay) overlay.remove();
    }

    function updateLoadingText(str) {
        const el = document.getElementById('gemini-loading-text');
        if (el) el.textContent = str;
    }

    function loadAllHistory() {
        const scrollContainer = findChatScroller();

        if (!scrollContainer || scrollContainer === document.body) {
            showToast('⚠️ 未找到聊天滚动区域,请先滚动一下页面再试', 4000);
            return;
        }

        console.log('Gemini优化: 已定位滚动容器', scrollContainer);
        showToast(`🎯 已定位滚动区域 (${scrollContainer.tagName})`, 1000);

        const startScrollHeight = scrollContainer.scrollHeight;
        const startScrollTop = scrollContainer.scrollTop;

        createLoadingOverlay();
        window._geminiStopLoading = false;

        let lastHeight = scrollContainer.scrollHeight;
        let noChangeCount = 0;
        const maxRetries = 5;

        const loadLoop = () => {
            if (window._geminiStopLoading) {
                removeLoadingOverlay();
                showToast('已停止加载');
                const currentHeight = scrollContainer.scrollHeight;
                scrollContainer.scrollTop = currentHeight - startScrollHeight + startScrollTop;
                return;
            }

            scrollContainer.scrollTop = 0;
            scrollContainer.dispatchEvent(new WheelEvent('wheel', { deltaY: -100, bubbles: true }));

            setTimeout(() => {
                const currentHeight = scrollContainer.scrollHeight;

                if (currentHeight > lastHeight) {
                    lastHeight = currentHeight;
                    noChangeCount = 0;
                    updateLoadingText(`已加载... (当前高度: ${currentHeight}px)`);
                    loadLoop();
                } else {
                    noChangeCount++;
                    updateLoadingText(`正在触底确认... (${noChangeCount}/${maxRetries})`);

                    if (noChangeCount >= maxRetries) {
                        removeLoadingOverlay();
                        showToast('✅ 历史记录加载完毕!', 3000);

                        const addedHeight = currentHeight - startScrollHeight;
                        scrollContainer.scrollTop = startScrollTop + addedHeight;

                    } else {
                        loadLoop();
                    }
                }
            }, 1200);
        };

        loadLoop();
    }

    // =========================================================================
    // 6. 悬浮按钮 (拖拽 + 记忆 + 开关)
    // =========================================================================

    function initFloatingButton() {
        // 1. 检查开关状态
        const isEnabled = GM_getValue(CONFIG_KEY_FLOAT_BTN, true);
        const btn = document.getElementById('gemini-float-load-btn');

        // 如果功能被禁用
        if (!isEnabled) {
            if (btn) btn.remove(); // 如果存在则移除
            return;
        }

        // 如果已存在且功能开启,则不重复创建
        if (btn) return;

        // 2. 创建按钮
        const newBtn = document.createElement('div');
        newBtn.id = 'gemini-float-load-btn';
        newBtn.title = '自动加载全部历史对话 (可拖拽)';

        const icon = document.createElement('mat-icon');
        icon.setAttribute('role', 'img');
        icon.className = 'mat-icon notranslate google-symbols mat-ligature-font';
        icon.setAttribute('aria-hidden', 'true');
        icon.textContent = 'vertical_align_top';
        newBtn.appendChild(icon);

        document.body.appendChild(newBtn);

        // 3. 读取位置
        const savedPos = localStorage.getItem(CONFIG_KEY_BTN_POS);
        if (savedPos) {
            try {
                const pos = JSON.parse(savedPos);
                const safeLeft = Math.min(Math.max(0, pos.left), window.innerWidth - 50);
                const safeTop = Math.min(Math.max(0, pos.top), window.innerHeight - 50);
                newBtn.style.left = safeLeft + 'px';
                newBtn.style.top = safeTop + 'px';
                newBtn.style.right = 'auto';
            } catch(e) {
                newBtn.style.top = '120px';
                newBtn.style.right = '24px';
            }
        } else {
            newBtn.style.top = '120px';
            newBtn.style.right = '24px';
        }

        // 4. 拖拽逻辑
        let isDragging = false;
        let hasMoved = false;
        let startX, startY, initialLeft, initialTop;

        newBtn.addEventListener('mousedown', (e) => {
            isDragging = true;
            hasMoved = false;
            startX = e.clientX;
            startY = e.clientY;

            const rect = newBtn.getBoundingClientRect();
            initialLeft = rect.left;
            initialTop = rect.top;

            newBtn.style.right = 'auto';
            newBtn.style.left = initialLeft + 'px';
            newBtn.style.top = initialTop + 'px';
            e.preventDefault();
        });

        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            const dx = e.clientX - startX;
            const dy = e.clientY - startY;
            if (Math.abs(dx) > 3 || Math.abs(dy) > 3) hasMoved = true;
            newBtn.style.left = (initialLeft + dx) + 'px';
            newBtn.style.top = (initialTop + dy) + 'px';
        });

        document.addEventListener('mouseup', () => {
            if (!isDragging) return;
            isDragging = false;
            if (hasMoved) {
                const rect = newBtn.getBoundingClientRect();
                localStorage.setItem(CONFIG_KEY_BTN_POS, JSON.stringify({ left: rect.left, top: rect.top }));
            }
        });

        newBtn.addEventListener('click', (e) => {
            if (!hasMoved) loadAllHistory();
        });
    }

    // =========================================================================
    // 7. 菜单命令 (Menu Command)
    // =========================================================================
    let menuCmdId = null;

    function updateMenuCommand() {
        // 如果之前注册过,先移除(防止重复)
        if (menuCmdId !== null) {
            GM_unregisterMenuCommand(menuCmdId);
        }

        const isEnabled = GM_getValue(CONFIG_KEY_FLOAT_BTN, true);
        // 根据当前状态显示相反的操作
        const menuText = isEnabled ? "🚫 隐藏悬浮加载按钮" : "🔘 显示悬浮加载按钮";

        menuCmdId = GM_registerMenuCommand(menuText, () => {
            const newState = !isEnabled;
            GM_setValue(CONFIG_KEY_FLOAT_BTN, newState);

            if (newState) {
                initFloatingButton();
                showToast('✅ 悬浮按钮已开启');
            } else {
                const btn = document.getElementById('gemini-float-load-btn');
                if (btn) btn.remove();
                showToast('🚫 悬浮按钮已隐藏');
            }

            // 点击后更新菜单文字
            updateMenuCommand();
        });
    }

    // =========================================================================
    // 8. 初始化
    // =========================================================================

    function init() {
        console.log('Gemini优化: V2.1 菜单设置版启动');

        // 注册菜单
        updateMenuCommand();

        // 尝试初始化按钮 (内部会检查开关状态)
        initFloatingButton();

        setInterval(() => {
            // 侧边栏逻辑
            const button = document.querySelector('button[data-test-id="studio-sidebar-button"]');
            if (button && !button.dataset.geminiOptimized) {
                button.dataset.geminiOptimized = 'true';
                button.addEventListener('mouseenter', showSidebar);
                button.addEventListener('mouseleave', hideSidebarDelayed);
            }
            const sidebar = document.querySelector('context-sidebar');
            if (sidebar) bindSidebarEvents(sidebar);

            // 按钮逻辑:持续检查 (如果被删除且配置为开启,则重建;如果配置为关闭且存在,则移除)
            const isEnabled = GM_getValue(CONFIG_KEY_FLOAT_BTN, true);
            const btn = document.getElementById('gemini-float-load-btn');

            if (isEnabled && !btn) {
                initFloatingButton();
            } else if (!isEnabled && btn) {
                btn.remove();
            }

        }, 1000);
    }

    init();

})();

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...