gemini 一直以来 canvas 有 2 个问题导致编程变成很不方便
一个项目有很多分文件,但是 gemini 每次找分文件只有 2 种方法
1.向上滑动寻找(极其不方便,尤其是上下文对话很长的时候)
2.点击右上角的此对话中的文件(没有更新顺序,不如 claude 的顺序层次分明)
每次打开很长的对话回答都会显示部分对话,向上滑动才会动态加载,但还是加载不全,导致那个此对话中的文件也没加载全
针对这 2 个问题,做出来一下油猴脚本
🚀 Gemini 优化助手 (Gemini Optimizer) 使用说明
简介
Gemini 优化助手 是一款专为 Google Gemini 网页版设计的油猴脚本。它旨在解决原版界面中文件列表查看不便、历史记录加载繁琐等痛点,提供更流畅、更现代化的交互体验。
✨ 核心功能
1. 📂 侧边栏文件列表优化
- 悬停即现:无需点击,只需将鼠标悬停在右上角的“文件”图标(或侧边栏区域),列表即会自动展开。
- 智能排序:自动读取文件的时间戳,将列表按时间倒序排列(最新的文件在最上面),找代码更方便。
- UI 美化:
- 将侧边栏改为纯白/深色适配的圆角悬浮卡片风格。
- 去除了原生按钮点击后难看的“深色圆圈”选中状态。
- 侧边栏不再挤占右侧空间,而是悬浮在顶层,视觉更清爽。
2. 📥 历史记录一键加载(独家功能)
- 独立悬浮按钮:屏幕右侧新增一个圆形的“置顶/加载”按钮。
- 一键“爬楼”:点击按钮,脚本会自动模拟滚动操作,一直向上翻阅直到加载完所有历史对话。
- 静默加载体验:
- 点击后会出现一个全屏遮罩(带动画),提示“正在挖掘历史记录”。
- 加载过程中页面不会乱跳,脚本在后台默默工作。
- 无缝归位:加载完成后,遮罩消失,页面会自动跳转回你点击按钮前看到的位置,实现无缝衔接。
3. 🖱️ 悬浮按钮自由拖拽
- 随心所欲:觉得按钮挡视线?按住它,拖到屏幕任意位置即可。
- 位置记忆:脚本会自动记住你放置按钮的位置,刷新页面或下次打开时,它依然在那里。
4. ⚙️ 个性化设置
- 开关控制:如果你暂时不需要“自动加载”功能,可以在油猴菜单中一键关闭悬浮按钮。
📖 操作指南
如何安装
- 确保浏览器已安装 Tampermonkey (篡改猴) 扩展。
- 新建一个脚本,将代码完整粘贴进去并保存(或通过链接安装)。
- 刷新 Gemini 页面即可生效。
如何查看文件列表
- 将鼠标移动到页面右上角的 📁 文件图标 上。
- 侧边栏会自动悬浮显示。
- 移开鼠标(且不悬停在侧边栏上)约 0.3 秒后,侧边栏自动消失。
如何加载全部历史记录
- 找到屏幕上的 圆形悬浮按钮(默认在右上方)。
- 点击 该按钮。
- 等待遮罩层的进度提示(通常几秒钟,取决于对话长度)。
- 看到“✅ 历史记录加载完毕”提示后,即可尽情浏览旧对话。
如何移动悬浮按钮
- 按住 悬浮按钮不放。
- 拖动到你喜欢的位置。
- 松开鼠标,位置自动保存。
如何隐藏/显示悬浮按钮
- 点击浏览器地址栏右侧的 Tampermonkey 图标。
- 在弹出的菜单中,找到 gemini 优化 下方的选项。
- 点击 🚫 隐藏悬浮加载按钮 即可隐藏。
- 如需恢复,再次点击该菜单(此时显示为 🔘 显示悬浮加载按钮)。
// ==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();
})();
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于