您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
模仿 Notion 目录交互,默认横线缩略,鼠标悬停展开目录
// ==UserScript== // @name AI Chat TOC (多平台 AI 对话目录助手) // @namespace http://tampermonkey.net/ // @version 2.0 // @description 模仿 Notion 目录交互,默认横线缩略,鼠标悬停展开目录 // @author John Qu // @license MIT // @match https://chat.openai.com/* // @match https://chatgpt.com/* // @match https://gemini.google.com/* // @match https://claude.ai/* // @grant none // ==/UserScript== (function () { 'use strict'; let messageSelector = ''; let textSelector = ''; switch (window.location.hostname) { case 'gemini.google.com': messageSelector = '.query-content'; textSelector = '.query-text'; break; case 'chat.openai.com': case 'chatgpt.com': messageSelector = 'div[data-message-author-role="user"]'; textSelector = 'div.whitespace-pre-wrap'; break; case 'claude.ai': messageSelector = 'div[data-testid="user-message"]'; textSelector = 'p[class="whitespace-pre-wrap break-words"]'; break; } // 创建目录容器 const tocContainer = document.createElement('div'); tocContainer.id = 'user-toc-container'; tocContainer.style.cssText = ` position: fixed; top: 80px; right: 20px; width: 40px; /* 默认宽度,用于显示横线缩略图 */ max-height: 80vh; overflow-y: hidden; /* 默认隐藏滚动条 */ background: #f7f7f8; border: 1px solid #dcdcdc; box-shadow: 0 4px 14px rgba(0,0,0,0.1); font-size: 14px; z-index: 9999; padding: 12px; border-radius: 8px; user-select: none; transition: width 0.3s ease-in-out, overflow-y 0.3s ease-in-out; /* 添加过渡效果 */ display: flex; flex-direction: column; justify-content: center; /* 垂直居中横线 */ align-items: center; /* 水平居中横线 */ `; // 创建横线缩略图容器 const thumbnailContainer = document.createElement('div'); thumbnailContainer.id = 'toc-thumbnail'; thumbnailContainer.style.cssText = ` display: flex; flex-direction: column; gap: 4px; /* 横线之间的间距 */ width: 100%; height: 100%; justify-content: center; align-items: center; `; // 初始创建几条横线 for (let i = 0; i < 4; i++) { // 可以根据需要调整横线的数量 const line = document.createElement('div'); line.style.cssText = ` width: 24px; height: 2px; background-color: #ccc; border-radius: 1px; `; thumbnailContainer.appendChild(line); } tocContainer.appendChild(thumbnailContainer); // 目录列表容器 const list = document.createElement('ul'); list.style.cssText = ` list-style: none; padding-left: 0; margin: 0; display: none; /* 默认隐藏 */ width: 100%; /* 确保列表在展开时撑满容器 */ `; tocContainer.appendChild(list); document.body.appendChild(tocContainer); // 鼠标悬停和离开事件 tocContainer.addEventListener('mouseenter', () => { tocContainer.style.width = '320px'; // 展开时的宽度 tocContainer.style.justifyContent = 'flex-start'; // 列表展开后顶部对齐 tocContainer.style.alignItems = 'flex-start'; // 列表展开后左侧对齐 thumbnailContainer.style.display = 'none'; // 隐藏横线缩略图 list.style.display = 'block'; // 显示目录列表 tocContainer.style.overflowY = 'auto'; // 允许滚动 updateTOC(); // 每次展开时更新目录,确保最新 }); tocContainer.addEventListener('mouseleave', () => { tocContainer.style.width = '40px'; // 收起时的宽度 tocContainer.style.justifyContent = 'center'; // 收起时横线垂直居中 tocContainer.style.alignItems = 'center'; // 收起时横线水平居中 thumbnailContainer.style.display = 'flex'; // 显示横线缩略图 list.style.display = 'none'; // 隐藏目录列表 tocContainer.style.overflowY = 'hidden'; // 隐藏滚动条 }); function updateTOC() { console.log('firstele',document.querySelectorAll(`${messageSelector}`)) list.textContent = ''; // 筛选出非空的用户消息,并过滤掉只包含空格、换行或空字符串的元素 const userMessages = Array.from(document.querySelectorAll(`${messageSelector}`)).filter(msgEl => { const contentDiv = msgEl.querySelector(`${textSelector}`); return contentDiv && contentDiv.innerText.trim(); }); console.log('hello world3', userMessages) let index = 0; userMessages.forEach((msgEl) => { const contentDiv = msgEl.querySelector(`${textSelector}`); if (contentDiv && contentDiv.innerText.trim()) { index++; const fullText = contentDiv.innerText.trim(); const shortText = fullText.slice(0, 60).replace(/\n/g, ' '); const li = document.createElement('li'); li.style.cssText = 'margin-bottom: 6px;'; const anchor = document.createElement('a'); anchor.textContent = `${index}. ${shortText}${fullText.length > 60 ? '…' : ''}`; anchor.title = fullText; anchor.href = '#'; anchor.style.cssText = ` display: inline-block; width: 100%; color: #0f79d0; text-decoration: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; `; anchor.onclick = (e) => { e.preventDefault(); msgEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); }; li.appendChild(anchor); list.appendChild(li); } }); } // 调整聊天内容区域宽度 function resizeChat() { const chatContainer = document.querySelector('div.relative.flex.h-full.max-w-full.flex-1.flex-col'); if (chatContainer) { chatContainer.style.cssText = ` max-width: 1200px; /* 调整最大宽度 */ margin: 0 auto; /* 居中显示 */ flex: 1; /* 保持 flex 属性 */ `; } } // 页面加载和URL变化时更新目录 window.addEventListener('load', () => setTimeout(() => { console.log('hello world!!!') updateTOC(); resizeChat(); }, 500)); // 延迟执行,确保页面元素加载完毕 let lastUrl = location.href; new MutationObserver(() => { const currentUrl = location.href; if (currentUrl !== lastUrl) { lastUrl = currentUrl; setTimeout(updateTOC, 1000); // URL变化时延迟更新 } }).observe(document.body, { childList: true, subtree: true }); // 监听用户消息数量变化,实时更新目录 const userListContainer = document.querySelector('main'); let lastUserCount = 0; if (userListContainer) { new MutationObserver(() => { const currentCount = document.querySelectorAll(`${messageSelector}`).length; if (currentCount !== lastUserCount) { lastUserCount = currentCount; setTimeout(updateTOC, 500); // 用户消息数量变化时延迟更新 } }).observe(userListContainer, { childList: true, subtree: true }); } })();