您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add a floating TOC for Gemini conversations
// ==UserScript== // @name Gemini TOC (Table of Contents) // @namespace http://tampermonkey.net/ // @version 1.1 // @description Add a floating TOC for Gemini conversations // @author You // @match https://gemini.google.com/* // @grant none // ==/UserScript== (function () { "use strict"; // 创建TOC容器 function createTOC() { const toc = document.createElement("div"); toc.id = "gemini-toc"; toc.style.cssText = ` position: fixed; top: 200px; right: 20px; width: 250px; max-height: 70vh; background: rgba(255, 255, 255, 0.6); border: 1px solid #ddd; border-radius: 6px; padding: 8px; z-index: 10000; opacity: 0.3; transition: opacity 0.3s ease; overflow-y: auto; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; // 添加标题 const title = document.createElement("h3"); title.textContent = "TOC"; title.style.cssText = ` margin: 0 0 6px 0; font-size: 11px; color: #666; text-align: center; border-bottom: 1px solid #eee; padding-bottom: 4px; font-weight: 500; `; toc.appendChild(title); // 添加鼠标悬停效果 toc.addEventListener("mouseenter", () => { toc.style.opacity = "1.0"; }); toc.addEventListener("mouseleave", () => { toc.style.opacity = "0.3"; }); document.body.appendChild(toc); return toc; } // 截取文本并添加省略号 function truncateText(text, maxLength = 30) { if (text.length <= maxLength) { return text; } return text.substring(0, maxLength) + "..."; } // 查找用户提问元素 function findUserPrompts() { const prompts = []; // 首先尝试查找带有 class="query-text-line" 的 p 标签 let queryTextElements = document.querySelectorAll( "p.query-text-line.ng-star-inserted" ); // if (queryTextElements.length === 0) { // queryTextElements = document.querySelectorAll('p[class*="query-text"]'); // } if (queryTextElements.length > 0) { const processedElements = new Set(); queryTextElements.forEach((element) => { // 如果这个元素已经被处理过,跳过 if (processedElements.has(element)) { return; } const text = element.textContent.trim(); if (text && text.length > 0) { // 查找连续的兄弟节点并合并文本 const mergedText = []; const elementsGroup = [element]; let currentElement = element; // 添加当前元素的文本 mergedText.push(text); processedElements.add(element); // 向后查找连续的兄弟节点 let nextSibling = currentElement.nextElementSibling; while (nextSibling && nextSibling.matches('p.query-text-line.ng-star-inserted')) { const siblingText = nextSibling.textContent.trim(); if (siblingText && siblingText.length > 0) { mergedText.push(siblingText); elementsGroup.push(nextSibling); processedElements.add(nextSibling); } nextSibling = nextSibling.nextElementSibling; } // 向前查找连续的兄弟节点(以防顺序不同) let prevSibling = currentElement.previousElementSibling; while (prevSibling && prevSibling.matches('p.query-text-line.ng-star-inserted') && !processedElements.has(prevSibling)) { const siblingText = prevSibling.textContent.trim(); if (siblingText && siblingText.length > 0) { mergedText.unshift(siblingText); elementsGroup.unshift(prevSibling); processedElements.add(prevSibling); } prevSibling = prevSibling.previousElementSibling; } // 将合并的文本和第一个元素添加到prompts中 prompts.push({ element: elementsGroup[0], // 使用第一个元素作为跳转目标 text: mergedText.join(' '), // 用空格连接所有文本 elementsGroup: elementsGroup // 保存所有相关元素,以备将来使用 }); } }); return prompts; } // // 尝试多种选择器来找到用户输入 // const selectors = [ // '[data-message-author-role="user"]', // ".user-message", // '[role="user"]', // ".message.user", // 'div[data-test-id*="user"]', // 'div[data-test-id*="prompt"]', // ".prompt-content", // ".user-input", // '[class*="user"][class*="message"]', // ]; // for (const selector of selectors) { // const elements = document.querySelectorAll(selector); // if (elements.length > 0) { // elements.forEach((element) => { // const text = element.textContent.trim(); // if (text && text.length > 0) { // prompts.push({ // element: element, // text: text, // }); // } // }); // break; // } // } // // 如果上述选择器都没找到,尝试通过文本内容和位置来识别 // if (prompts.length === 0) { // // 尝试查找所有可能包含用户输入的元素 // const allElements = document.querySelectorAll("p, div, span"); // allElements.forEach((element) => { // const text = element.textContent.trim(); // // 简单启发式:查找可能是用户输入的元素 // if ( // text && // text.length > 10 && // text.length < 1000 && // !text.includes("Gemini") && // !text.includes("Google") && // !text.includes("AI") && // element.children.length === 0 // ) { // // 检查是否在对话容器中或者有相关的类名 // const parent = element.closest( // '[role="main"], .conversation, .chat, main' // ); // const hasUserClass = // element.className && // (element.className.includes("user") || // element.className.includes("prompt") || // element.className.includes("query")); // if (parent || hasUserClass) { // prompts.push({ // element: element, // text: text, // }); // } // } // }); // } return prompts; } // 更新TOC内容 function updateTOC(tocContainer) { const prompts = findUserPrompts(); // 清除现有内容(保留标题) const title = tocContainer.querySelector("h3"); // 使用安全的DOM操作替代innerHTML while (tocContainer.firstChild) { tocContainer.removeChild(tocContainer.firstChild); } tocContainer.appendChild(title); if (prompts.length === 0) { const noContent = document.createElement("div"); noContent.textContent = "暂无对话内容"; noContent.style.cssText = ` color: #999; font-style: italic; text-align: center; padding: 15px 0; `; tocContainer.appendChild(noContent); return; } // 创建TOC条目 prompts.forEach((prompt, index) => { const item = document.createElement("div"); item.className = "toc-item"; item.style.cssText = ` padding: 6px 8px; margin: 2px 0; background: rgba(240, 240, 240, 0.5); border-radius: 3px; cursor: pointer; transition: background-color 0.2s ease; font-size: 12px; line-height: 1.3; border-left: 2px solid #4285f4; `; // 添加序号和文本 const itemText = document.createElement("span"); itemText.textContent = `${index + 1}. ${truncateText(prompt.text, 25)}`; item.appendChild(itemText); // 添加悬停效果 item.addEventListener("mouseenter", () => { item.style.backgroundColor = "rgba(66, 133, 244, 0.1)"; }); item.addEventListener("mouseleave", () => { item.style.backgroundColor = "rgba(240, 240, 240, 0.5)"; }); // 添加点击跳转功能 item.addEventListener("click", () => { prompt.element.scrollIntoView({ behavior: "smooth", block: "center", }); // 高亮目标元素 prompt.element.style.transition = "background-color 0.5s ease"; const originalBg = prompt.element.style.backgroundColor; prompt.element.style.backgroundColor = "rgba(66, 133, 244, 0.2)"; setTimeout(() => { prompt.element.style.backgroundColor = originalBg; }, 2000); }); tocContainer.appendChild(item); }); } // 初始化 function init() { // 等待页面加载完成 if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); return; } // 创建TOC const tocContainer = createTOC(); // 初始更新 setTimeout(() => { updateTOC(tocContainer); }, 500); // 监听DOM变化以自动更新TOC const observer = new MutationObserver(() => { // 防抖:延迟更新以避免频繁刷新 clearTimeout(window.tocUpdateTimeout); window.tocUpdateTimeout = setTimeout(() => { updateTOC(tocContainer); }, 500); }); // 开始观察 observer.observe(document.body, { childList: true, subtree: true, attributes: false, }); } // 启动脚本 init(); })();