您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
为 GPT 对话生成右侧大纲视图,提取问题前16个字作为标题
// ==UserScript== // @name GPT 大纲生成器 // @namespace http://tampermonkey.net/ // @version 1.7 // @description 为 GPT 对话生成右侧大纲视图,提取问题前16个字作为标题 // @author YungVenuz // @license AGPL-3.0-or-later // @match https://chatgpt.com/* // @match https://chat.deepseek.com/* // @match https://gemini.google.com/* // @match https://kimi.moonshot.cn/* // @include https://ying.baichuan-ai.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=openai.com // @grant none // ==/UserScript== (function () { 'use strict'; function $(param) { // DOM Ready 逻辑 if (typeof param === 'function') { if (document.readyState === 'complete' || document.readyState === 'interactive') { param(); } else { document.addEventListener('DOMContentLoaded', param); } return; } // 选择器逻辑 if (typeof param === 'string') { const elements = document.querySelectorAll(param); return { elements, hide() { this.elements.forEach(el => el.style.display = 'none'); return this; }, click(fn) { this.elements.forEach(el => el.addEventListener('click', fn)); return this; } }; } } /** * ChatGPT大纲生成器类 */ class ChatGPTOutlineGenerator { constructor() { this.outlineContainer = null; this.toggleButton = null; this.styleElement = null; this.cssStyles = ` .outline-container { color: #000000; position: fixed; top: 70px; right: 20px; width: 280px; max-height: calc(100vh - 100px); background-color: rgba(247, 247, 248, 0.85); border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); z-index: 1000; overflow-y: auto; overflow-x: hidden; /* 防止水平滚动 */ transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); font-family: 'Söhne', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif; opacity: 0.95; backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); } .outline-container:hover { opacity: 1; background-color: rgba(247, 247, 248, 0.98); box-shadow: 0 6px 24px rgba(0, 0, 0, 0.12); transform: translateY(-2px); } .dark .outline-container { background-color: rgba(52, 53, 65, 0.85); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25); } .dark .outline-container:hover { background-color: rgba(52, 53, 65, 0.98); box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35); } .outline-header { padding: 16px; font-weight: 600; font-size: 16px; border-bottom: 1px solid rgba(0, 0, 0, 0.05); display: flex; justify-content: space-between; align-items: center; position: sticky; top: 0; background: inherit; border-radius: 12px 12px 0 0; z-index: 2; } .dark .outline-header { border-bottom: 1px solid rgba(255, 255, 255, 0.05); color: #ececf1; } .outline-title { display: flex; align-items: center; gap: 8px; } .outline-title-icon { color: #10a37f; } .outline-items { padding: 8px 0; } .outline-item { padding: 10px 16px; cursor: pointer; transition: all 0.2s ease; font-size: 14px; border-left: 3px solid transparent; display: flex; align-items: center; margin: 2px 0; border-radius: 0 4px 4px 0; box-sizing: border-box; /* 确保padding不会增加元素宽度 */ width: 100%; /* 确保宽度不超过父容器 */ } .outline-item:hover { background-color: rgba(0, 0, 0, 0.05); transform: translateX(2px); } .dark .outline-item:hover { background-color: rgba(255, 255, 255, 0.05); } .outline-item.active { border-left-color: #10a37f; background-color: rgba(16, 163, 127, 0.1); font-weight: 500; } .outline-item-icon { margin-right: 10px; color: #10a37f; transition: transform 0.2s ease; } .outline-item:hover .outline-item-icon { transform: scale(1.1); } .outline-item-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; line-height: 1.4; max-width: calc(100% - 30px); /* 减去图标和边距的宽度 */ } .dark .outline-item-text { color: #ececf1; } .outline-toggle { position: fixed; top: 70px; right: 20px; width: 42px; height: 42px; border-radius: 50%; background-color: rgba(16, 163, 127, 0.9); color: white; display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 1001; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15); transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); } .outline-toggle:hover { transform: scale(1.08); background-color: #10a37f; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); } .outline-toggle svg { width: 20px; height: 20px; transition: transform 0.3s ease; } .outline-toggle:hover svg { transform: rotate(90deg); } .outline-close { cursor: pointer; opacity: 0.7; transition: all 0.2s ease; padding: 4px; border-radius: 4px; } .outline-close:hover { opacity: 1; background-color: rgba(0, 0, 0, 0.05); } .dark .outline-close:hover { background-color: rgba(255, 255, 255, 0.1); } .outline-empty { padding: 20px 16px; text-align: center; color: #888; font-style: italic; font-size: 14px; } .dark .outline-empty { color: #aaa; } @media (max-width: 1400px) { .outline-container { width: 250px; } } @media (max-width: 1200px) { .outline-container { width: 220px; } } @media (max-width: 768px) { .outline-container { display: none; } }`; } /** * 初始化大纲生成器 */ init() { // 等待页面加载完成 if (!document.querySelector('main')) { setTimeout(() => this.init(), 50); return; } this.addStyles(); this.outlineContainer = this.createOutlineContainer(); this.toggleButton = this.createToggleButton(); // 初始化大纲 setTimeout(() => this.generateOutlineItems(), 50); // 设置初始暗黑模式状态 this.outlineContainer.classList.toggle('dark', this.detectDarkMode()); // 监听暗黑模式变化 this.observeDarkModeChanges(); // 监听新消息 this.observeNewMessages(); // 监听滚动以高亮当前可见的消息 this.observeScroll(); } /** * 添加样式到页面 */ addStyles() { this.styleElement = document.createElement('style'); this.styleElement.textContent = this.cssStyles; document.head.appendChild(this.styleElement); } /** * 创建大纲容器 - 使用DOM API * @returns {HTMLElement} 大纲容器元素 */ createOutlineContainer() { const container = document.createElement('div'); container.className = 'outline-container'; // 创建头部 const header = document.createElement('div'); header.className = 'outline-header'; // 创建标题 const title = document.createElement('div'); title.className = 'outline-title'; // 创建标题图标 const titleIcon = document.createElement('span'); titleIcon.className = 'outline-title-icon'; const titleSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); titleSvg.setAttribute('width', '16'); titleSvg.setAttribute('height', '16'); titleSvg.setAttribute('viewBox', '0 0 24 24'); titleSvg.setAttribute('fill', 'none'); const titlePath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); titlePath.setAttribute('d', 'M4 6H20M4 12H20M4 18H14'); titlePath.setAttribute('stroke', 'currentColor'); titlePath.setAttribute('stroke-width', '2'); titlePath.setAttribute('stroke-linecap', 'round'); titlePath.setAttribute('stroke-linejoin', 'round'); titleSvg.appendChild(titlePath); titleIcon.appendChild(titleSvg); // 创建标题文本 const titleText = document.createElement('span'); titleText.textContent = '对话大纲'; // 组装标题 title.appendChild(titleIcon); title.appendChild(titleText); // 创建关闭按钮 const closeBtn = document.createElement('span'); closeBtn.className = 'outline-close'; const closeSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); closeSvg.setAttribute('width', '16'); closeSvg.setAttribute('height', '16'); closeSvg.setAttribute('viewBox', '0 0 24 24'); closeSvg.setAttribute('fill', 'none'); const closePath1 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); closePath1.setAttribute('d', 'M18 6L6 18'); closePath1.setAttribute('stroke', 'currentColor'); closePath1.setAttribute('stroke-width', '2'); closePath1.setAttribute('stroke-linecap', 'round'); closePath1.setAttribute('stroke-linejoin', 'round'); const closePath2 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); closePath2.setAttribute('d', 'M6 6L18 18'); closePath2.setAttribute('stroke', 'currentColor'); closePath2.setAttribute('stroke-width', '2'); closePath2.setAttribute('stroke-linecap', 'round'); closePath2.setAttribute('stroke-linejoin', 'round'); closeSvg.appendChild(closePath1); closeSvg.appendChild(closePath2); closeBtn.appendChild(closeSvg); // 组装头部 header.appendChild(title); header.appendChild(closeBtn); // 创建大纲项容器 const outlineItems = document.createElement('div'); outlineItems.className = 'outline-items'; // 组装容器 container.appendChild(header); container.appendChild(outlineItems); document.body.appendChild(container); // 添加关闭事件 closeBtn.addEventListener('click', () => { container.style.display = 'none'; this.toggleButton.style.display = 'flex'; }); return container; } /** * 创建切换按钮 - 使用DOM API * @returns {HTMLElement} 切换按钮元素 */ createToggleButton() { const button = document.createElement('div'); button.className = 'outline-toggle'; const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('width', '24'); svg.setAttribute('height', '24'); svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('fill', 'none'); const path1 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path1.setAttribute('d', 'M4 6H20'); path1.setAttribute('stroke', 'currentColor'); path1.setAttribute('stroke-width', '2'); path1.setAttribute('stroke-linecap', 'round'); const path2 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path2.setAttribute('d', 'M4 12H20'); path2.setAttribute('stroke', 'currentColor'); path2.setAttribute('stroke-width', '2'); path2.setAttribute('stroke-linecap', 'round'); const path3 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path3.setAttribute('d', 'M4 18H20'); path3.setAttribute('stroke', 'currentColor'); path3.setAttribute('stroke-width', '2'); path3.setAttribute('stroke-linecap', 'round'); svg.appendChild(path1); svg.appendChild(path2); svg.appendChild(path3); button.appendChild(svg); document.body.appendChild(button); // 添加点击事件 button.addEventListener('click', () => { this.outlineContainer.style.display = 'block'; button.style.display = 'none'; // 重新生成大纲,确保最新状态 this.generateOutlineItems(); }); return button; } /** * 提取问题文本的前20个字符 * @param {string} text 问题文本 * @returns {string} 提取后的标题 */ extractQuestionTitle(text) { // 去除空白字符 const trimmed = text.trim(); // 如果文本为空,返回默认文本 if (!trimmed) return "空白问题"; // 提取前20个字符,如果不足20个则全部返回 return trimmed.length > 20 ? trimmed.substring(0, 20) + '...' : trimmed; } /** * 生成大纲项 - 使用DOM API */ generateOutlineItems() { const outlineItems = this.outlineContainer.querySelector('.outline-items'); // 清空现有内容 while (outlineItems.firstChild) { outlineItems.removeChild(outlineItems.firstChild); } // 获取所有用户消息 const userMessages = document.querySelectorAll('[data-message-author-role="user"]'); if (userMessages.length === 0) { const emptyDiv = document.createElement('div'); emptyDiv.className = 'outline-empty'; emptyDiv.textContent = '暂无对话内容'; outlineItems.appendChild(emptyDiv); return; } userMessages.forEach((message, index) => { const messageText = message.querySelector('.whitespace-pre-wrap')?.textContent || ''; const title = this.extractQuestionTitle(messageText); const item = document.createElement('div'); item.className = 'outline-item'; item.dataset.index = index; item.dataset.messageId = message.id || `message-${index}`; // 创建图标 const iconSpan = document.createElement('span'); iconSpan.className = 'outline-item-icon'; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 24 24'); iconSvg.setAttribute('fill', 'none'); const iconPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); iconPath.setAttribute('d', 'M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z'); iconPath.setAttribute('stroke', 'currentColor'); iconPath.setAttribute('fill', 'currentColor'); iconSvg.appendChild(iconPath); iconSpan.appendChild(iconSvg); // 创建文本 const textSpan = document.createElement('span'); textSpan.className = 'outline-item-text'; textSpan.textContent = `${index + 1}. ${title}`; // 组装项目 item.appendChild(iconSpan); item.appendChild(textSpan); // 添加点击事件 item.addEventListener('click', () => this.handleItemClick(item, message)); outlineItems.appendChild(item); }); // 检查是否有可见的消息,并高亮对应的大纲项 this.highlightVisibleItem(); } /** * 处理大纲项点击事件 * @param {HTMLElement} item 点击的大纲项 * @param {HTMLElement} message 对应的消息元素 */ handleItemClick(item, message) { // 滚动到对应的消息 message.scrollIntoView({ behavior: 'smooth', block: 'center' }); // 高亮当前项 this.highlightItem(item); // 添加临时高亮效果 message.style.transition = 'background-color 0.5s'; message.style.backgroundColor = 'rgba(16, 163, 127, 0.1)'; setTimeout(() => { message.style.backgroundColor = ''; }, 1500); } /** * 高亮指定的大纲项 * @param {HTMLElement} item 要高亮的大纲项 */ highlightItem(item) { document.querySelectorAll('.outline-item').forEach(el => { el.classList.remove('active'); }); item.classList.add('active'); } /** * 监听页面滚动,高亮当前可见的消息对应的大纲项 */ observeScroll() { let scrollTimer = null; window.addEventListener('scroll', () => { // 使用防抖技术减少滚动事件处理频率 if (scrollTimer) clearTimeout(scrollTimer); scrollTimer = setTimeout(() => { this.highlightVisibleItem(); }, 100); }); } /** * 高亮当前可见的消息对应的大纲项 */ highlightVisibleItem() { const userMessages = document.querySelectorAll('[data-message-author-role="user"]'); if (!userMessages.length) return; // 找到当前视口中最靠近顶部的消息 let closestMessage = null; let closestDistance = Infinity; const viewportHeight = window.innerHeight; const viewportMiddle = viewportHeight / 2; userMessages.forEach(message => { const rect = message.getBoundingClientRect(); // 计算消息中心点到视口中心的距离 const distance = Math.abs((rect.top + rect.bottom) / 2 - viewportMiddle); // 如果消息在视口内且距离更近 if (rect.top < viewportHeight && rect.bottom > 0 && distance < closestDistance) { closestDistance = distance; closestMessage = message; } }); if (closestMessage) { // 找到对应的大纲项并高亮 const index = Array.from(userMessages).indexOf(closestMessage); const outlineItem = this.outlineContainer.querySelector(`.outline-item[data-index="${index}"]`); if (outlineItem) { this.highlightItem(outlineItem); } } } /** * 检测暗黑模式 * @returns {boolean} 是否为暗黑模式 */ detectDarkMode() { return document.documentElement.classList.contains('dark'); } /** * 监听暗黑模式变化 */ observeDarkModeChanges() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.attributeName === 'class') { const isDarkMode = this.detectDarkMode(); this.outlineContainer.classList.toggle('dark', isDarkMode); } }); }); observer.observe(document.documentElement, { attributes: true }); } /** * 监听新消息 */ observeNewMessages() { const observer = new MutationObserver((mutations) => { let shouldUpdate = false; mutations.forEach(mutation => { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE && (node.querySelector('[data-message-author-role="user"]') || node.hasAttribute && node.hasAttribute('data-message-author-role'))) { shouldUpdate = true; break; } } } }); if (shouldUpdate) { setTimeout(() => this.generateOutlineItems(), 50); // 延迟执行,确保DOM已更新 } }); // 监听整个聊天容器 const chatContainer = document.querySelector('main'); if (chatContainer) { observer.observe(chatContainer, { childList: true, subtree: true }); } } } /** * DeepSeek大纲生成器类 */ class DeepSeekOutlineGenerator extends ChatGPTOutlineGenerator { constructor() { super(); // 继承ChatGPT大纲生成器的所有属性和方法 this.currentUrl = window.location.href; } /** * 初始化大纲生成器 */ init() { // 等待页面加载完成 if (!document.querySelector('.dad65929')) { setTimeout(() => this.init(), 50); return; } this.addStyles(); this.outlineContainer = this.createOutlineContainer(); this.toggleButton = this.createToggleButton(); // 初始化大纲 setTimeout(() => this.generateOutlineItems(), 50); // 监听新消息 this.observeNewMessages(); // 监听滚动以高亮当前可见的消息 this.observeScroll(); // 监听URL变化 this.observeUrlChanges(); } /** * 生成大纲项 */ generateOutlineItems() { const outlineItems = this.outlineContainer.querySelector('.outline-items'); // 清空现有内容 while (outlineItems.firstChild) { outlineItems.removeChild(outlineItems.firstChild); } // 获取所有用户消息 - 使用更新后的选择器 const chatContainer = document.querySelector('.dad65929'); if (!chatContainer) return; // 获取所有直接子div const allDivs = Array.from(chatContainer.children).filter(el => el.tagName === 'DIV'); // 筛选出奇数位置的div(索引从0开始,所以是偶数索引) const userMessageContainers = allDivs.filter((_, index) => index % 2 === 0); // 从每个容器中提取第一个div作为用户消息 const userMessages = userMessageContainers.map(container => { const firstDiv = container.querySelector('div'); return firstDiv; }).filter(Boolean); // 过滤掉可能的null值 if (userMessages.length === 0) { const emptyDiv = document.createElement('div'); emptyDiv.className = 'outline-empty'; emptyDiv.textContent = '暂无对话内容'; outlineItems.appendChild(emptyDiv); return; } userMessages.forEach((message, index) => { // 提取消息文本 const messageText = message.textContent || ''; const title = this.extractQuestionTitle(messageText); const item = document.createElement('div'); item.className = 'outline-item'; item.dataset.index = index; item.dataset.messageId = `deepseek-message-${index}`; // 创建图标 const iconSpan = document.createElement('span'); iconSpan.className = 'outline-item-icon'; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 24 24'); iconSvg.setAttribute('fill', 'none'); const iconPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); iconPath.setAttribute('d', 'M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z'); iconPath.setAttribute('stroke', 'currentColor'); iconPath.setAttribute('fill', 'currentColor'); iconSvg.appendChild(iconPath); iconSpan.appendChild(iconSvg); // 创建文本 const textSpan = document.createElement('span'); textSpan.className = 'outline-item-text'; textSpan.textContent = `${index + 1}. ${title}`; // 组装项目 item.appendChild(iconSpan); item.appendChild(textSpan); // 添加点击事件 item.addEventListener('click', () => this.handleItemClick(item, message)); outlineItems.appendChild(item); }); // 检查是否有可见的消息,并高亮对应的大纲项 this.highlightVisibleItem(); } /** * 监听新消息 - 更新后的DeepSeek版本 */ observeNewMessages() { const observer = new MutationObserver((mutations) => { let shouldUpdate = false; mutations.forEach(mutation => { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { shouldUpdate = true; } }); if (shouldUpdate) { setTimeout(() => this.generateOutlineItems(), 50); } }); // 监听整个聊天容器 const chatContainer = document.querySelector('.dad65929'); if (chatContainer) { observer.observe(chatContainer, { childList: true, subtree: true }); } } /** * 监听URL变化 */ observeUrlChanges() { // 使用setInterval定期检查URL是否变化 setInterval(() => { const currentUrl = window.location.href; if (this.currentUrl !== currentUrl) { console.log('URL changed, reinitializing outline generator'); this.currentUrl = currentUrl; // 清空现有大纲 const outlineItems = this.outlineContainer.querySelector('.outline-items'); while (outlineItems && outlineItems.firstChild) { outlineItems.removeChild(outlineItems.firstChild); } // 等待新页面加载完成后重新生成大纲 setTimeout(() => this.generateOutlineItems(), 500); } }, 500); // 每秒检查一次 } /** * 高亮当前可见的消息对应的大纲项 - 更新后的DeepSeek版本 */ highlightVisibleItem() { // 获取所有用户消息 const chatContainer = document.querySelector('.dad65929'); if (!chatContainer) return; const allDivs = Array.from(chatContainer.children).filter(el => el.tagName === 'DIV'); const userMessageContainers = allDivs.filter((_, index) => index % 2 === 0); const userMessages = userMessageContainers.map(container => { return container.querySelector('div'); }).filter(Boolean); if (!userMessages.length) return; // 找到当前视口中最靠近中心的消息 let closestMessage = null; let closestDistance = Infinity; const viewportHeight = window.innerHeight; const viewportMiddle = viewportHeight / 2; userMessages.forEach(message => { const rect = message.getBoundingClientRect(); // 计算消息中心点到视口中心的距离 const distance = Math.abs((rect.top + rect.bottom) / 2 - viewportMiddle); // 如果消息在视口内且距离更近 if (rect.top < viewportHeight && rect.bottom > 0 && distance < closestDistance) { closestDistance = distance; closestMessage = message; } }); if (closestMessage) { // 找到对应的大纲项并高亮 const index = userMessages.indexOf(closestMessage); const outlineItem = this.outlineContainer.querySelector(`.outline-item[data-index="${index}"]`); if (outlineItem) { this.highlightItem(outlineItem); } } } /** * 处理大纲项点击事件 */ handleItemClick(item, message) { // 高亮被点击的项 this.highlightItem(item); // 滚动到对应的消息 if (message) { message.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } } /** * Gemini大纲生成器类 */ class GeminiOutlineGenerator extends ChatGPTOutlineGenerator { constructor() { super(); // 继承ChatGPT大纲生成器的所有属性和方法 } /** * 初始化大纲生成器 */ init() { // 等待页面加载完成 if (!document.querySelector('chat-window')) { setTimeout(() => this.init(), 50); return; } this.addStyles(); this.outlineContainer = this.createOutlineContainer(); this.toggleButton = this.createToggleButton(); // 初始化大纲 setTimeout(() => this.generateOutlineItems(), 50); // 监听新消息 this.observeNewMessages(); // 监听滚动以高亮当前可见的消息 this.observeScroll(); } /** * 生成大纲项 - 使用DOM API */ generateOutlineItems() { const outlineItems = this.outlineContainer.querySelector('.outline-items'); // 清空现有内容 while (outlineItems.firstChild) { outlineItems.removeChild(outlineItems.firstChild); } // 获取所有用户消息 - 使用更新后的Gemini特定的选择器 const userMessages = document.querySelectorAll('user-query .user-query-bubble-container'); if (userMessages.length === 0) { const emptyDiv = document.createElement('div'); emptyDiv.className = 'outline-empty'; emptyDiv.textContent = '暂无对话内容'; outlineItems.appendChild(emptyDiv); return; } userMessages.forEach((message, index) => { // 提取消息文本 - 适应Gemini的新DOM结构 const messageText = message.querySelector('.query-text')?.textContent || ''; const title = this.extractQuestionTitle(messageText); const item = document.createElement('div'); item.className = 'outline-item'; item.dataset.index = index; item.dataset.messageId = `gemini-message-${index}`; // 创建图标 const iconSpan = document.createElement('span'); iconSpan.className = 'outline-item-icon'; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 24 24'); iconSvg.setAttribute('fill', 'none'); const iconPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); iconPath.setAttribute('d', 'M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z'); iconPath.setAttribute('stroke', 'currentColor'); iconPath.setAttribute('fill', 'currentColor'); iconSvg.appendChild(iconPath); iconSpan.appendChild(iconSvg); // 创建文本 const textSpan = document.createElement('span'); textSpan.className = 'outline-item-text'; textSpan.textContent = `${index + 1}. ${title}`; // 组装项目 item.appendChild(iconSpan); item.appendChild(textSpan); // 添加点击事件 item.addEventListener('click', () => this.handleItemClick(item, message)); outlineItems.appendChild(item); }); // 检查是否有可见的消息,并高亮对应的大纲项 this.highlightVisibleItem(); } /** * 监听新消息 - Gemini版本 */ observeNewMessages() { const observer = new MutationObserver((mutations) => { let shouldUpdate = false; mutations.forEach(mutation => { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE && (node.querySelector('user-query') || node.tagName && node.tagName.toLowerCase() === 'user-query')) { shouldUpdate = true; break; } } } }); if (shouldUpdate) { setTimeout(() => this.generateOutlineItems(), 50); } }); // 监听整个聊天容器 const chatContainer = document.querySelector('chat-window-content'); if (chatContainer) { observer.observe(chatContainer, { childList: true, subtree: true }); } // 监听URL变化,因为切换对话可能会改变URL this.observeUrlChanges(); } /** * 监听URL变化 - 用于检测对话切换 */ observeUrlChanges() { let lastUrl = location.href; // 创建一个新的MutationObserver来监视URL变化 const urlObserver = new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; // URL变化后,等待一段时间再更新大纲,确保新对话已加载 setTimeout(() => this.generateOutlineItems(), 50); } }); // 监听整个文档的变化 urlObserver.observe(document, { subtree: true, childList: true }); // 使用history API监听导航事件 const originalPushState = history.pushState; const originalReplaceState = history.replaceState; const self = this; history.pushState = function () { originalPushState.apply(this, arguments); setTimeout(() => self.generateOutlineItems(), 50); }; history.replaceState = function () { originalReplaceState.apply(this, arguments); setTimeout(() => self.generateOutlineItems(), 50); }; // 监听popstate事件(浏览器的前进/后退按钮) window.addEventListener('popstate', () => { setTimeout(() => this.generateOutlineItems(), 50); }); } } /** * 百川AI大纲生成器类 */ class BaichuanOutlineGenerator extends ChatGPTOutlineGenerator { constructor() { super(); // 继承ChatGPT大纲生成器的所有属性和方法 } /** * 初始化大纲生成器 */ init() { // 等待页面加载完成 if (!document.querySelector('#chat-list')) { setTimeout(() => this.init(), 50); return; } this.addStyles(); this.outlineContainer = this.createOutlineContainer(); this.toggleButton = this.createToggleButton(); // 初始化大纲 setTimeout(() => this.generateOutlineItems(), 50); // 监听新消息和对话切换 this.observeNewMessages(); // 监听滚动以高亮当前可见的消息 this.observeScroll(); } /** * 生成大纲项 - 使用DOM API */ generateOutlineItems() { const outlineItems = this.outlineContainer.querySelector('.outline-items'); // 清空现有内容 while (outlineItems.firstChild) { outlineItems.removeChild(outlineItems.firstChild); } // 获取所有用户消息 - 使用百川AI特定的选择器 const userMessages = document.querySelectorAll('#chat-list [data-type="prompt-item"]'); if (userMessages.length === 0) { const emptyDiv = document.createElement('div'); emptyDiv.className = 'outline-empty'; emptyDiv.textContent = '暂无对话内容'; outlineItems.appendChild(emptyDiv); return; } userMessages.forEach((message, index) => { // 提取消息文本 - 适应百川AI的DOM结构 const messageText = message.querySelector('.prompt-text-item')?.textContent || ''; const title = this.extractQuestionTitle(messageText); const item = document.createElement('div'); item.className = 'outline-item'; item.dataset.index = index; item.dataset.messageId = `baichuan-message-${index}`; // 创建图标 const iconSpan = document.createElement('span'); iconSpan.className = 'outline-item-icon'; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 24 24'); iconSvg.setAttribute('fill', 'none'); const iconPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); iconPath.setAttribute('d', 'M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z'); iconPath.setAttribute('stroke', 'currentColor'); iconPath.setAttribute('fill', 'currentColor'); iconSvg.appendChild(iconPath); iconSpan.appendChild(iconSvg); // 创建文本 const textSpan = document.createElement('span'); textSpan.className = 'outline-item-text'; textSpan.textContent = `${index + 1}. ${title}`; // 组装项目 item.appendChild(iconSpan); item.appendChild(textSpan); // 添加点击事件 item.addEventListener('click', () => this.handleItemClick(item, message)); outlineItems.appendChild(item); }); // 检查是否有可见的消息,并高亮对应的大纲项 this.highlightVisibleItem(); } /** * 监听新消息 - 百川AI版本 */ observeNewMessages() { const observer = new MutationObserver((mutations) => { let shouldUpdate = false; mutations.forEach(mutation => { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE && (node.hasAttribute && node.hasAttribute('data-type') && node.getAttribute('data-type') === 'prompt-item')) { shouldUpdate = true; break; } } } // 检查属性变化,可能是对话切换导致的变化 if (mutation.type === 'attributes' && (mutation.attributeName === 'data-type' || mutation.attributeName === 'class')) { shouldUpdate = true; } }); if (shouldUpdate) { setTimeout(() => this.generateOutlineItems(), 50); } }); // 监听整个聊天容器 const chatContainer = document.querySelector('#chat-list'); if (chatContainer) { observer.observe(chatContainer, { childList: true, subtree: true, attributes: true, // 添加属性监听 attributeFilter: ['data-type', 'class'] // 监听特定属性变化 }); } // 监听URL变化,因为切换对话可能会改变URL this.observeUrlChanges(); // 监听点击事件,可能是通过点击切换对话 this.observeClickEvents(); } /** * 监听URL变化 - 用于检测对话切换 */ observeUrlChanges() { let lastUrl = location.href; // 创建一个新的MutationObserver来监视URL变化 const urlObserver = new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; // URL变化后,等待一段时间再更新大纲,确保新对话已加载 setTimeout(() => this.generateOutlineItems(), 50); } }); // 监听整个文档的变化 urlObserver.observe(document, { subtree: true, childList: true }); // 使用history API监听导航事件 const originalPushState = history.pushState; const originalReplaceState = history.replaceState; const self = this; history.pushState = function () { originalPushState.apply(this, arguments); setTimeout(() => self.generateOutlineItems(), 50); }; history.replaceState = function () { originalReplaceState.apply(this, arguments); setTimeout(() => self.generateOutlineItems(), 50); }; // 监听popstate事件(浏览器的前进/后退按钮) window.addEventListener('popstate', () => { setTimeout(() => this.generateOutlineItems(), 50); }); } /** * 监听点击事件 - 用于检测对话切换 */ observeClickEvents() { // 监听可能导致对话切换的点击事件 document.addEventListener('click', (event) => { // 检查是否点击了对话列表项 const chatItem = event.target.closest('[role="button"]'); if (chatItem) { // 延迟更新大纲,确保对话已切换 setTimeout(() => this.generateOutlineItems(), 50); } }); } /** * 高亮当前可见的消息对应的大纲项 - 百川AI版本 */ highlightVisibleItem() { const userMessages = document.querySelectorAll('#chat-list [data-type="prompt-item"]'); if (!userMessages.length) return; // 找到当前视口中最靠近顶部的消息 let closestMessage = null; let closestDistance = Infinity; const viewportHeight = window.innerHeight; const viewportMiddle = viewportHeight / 2; userMessages.forEach(message => { const rect = message.getBoundingClientRect(); // 计算消息中心点到视口中心的距离 const distance = Math.abs((rect.top + rect.bottom) / 2 - viewportMiddle); // 如果消息在视口内且距离更近 if (rect.top < viewportHeight && rect.bottom > 0 && distance < closestDistance) { closestDistance = distance; closestMessage = message; } }); if (closestMessage) { // 找到对应的大纲项并高亮 const index = Array.from(userMessages).indexOf(closestMessage); const outlineItem = this.outlineContainer.querySelector(`.outline-item[data-index="${index}"]`); if (outlineItem) { this.highlightItem(outlineItem); } } } } /** * Kimi大纲生成器类 */ class KimiOutlineGenerator extends ChatGPTOutlineGenerator { constructor() { super(); // 继承ChatGPT大纲生成器的所有属性和方法 this.currentUrl = window.location.href; } /** * 初始化大纲生成器 */ init() { // 等待页面加载完成 if (!document.querySelector('.chat-content-list')) { setTimeout(() => this.init(), 50); return; } this.addStyles(); this.outlineContainer = this.createOutlineContainer(); this.toggleButton = this.createToggleButton(); // 初始化大纲 setTimeout(() => this.generateOutlineItems(), 50); // 监听新消息 this.observeNewMessages(); // 监听滚动以高亮当前可见的消息 this.observeScroll(); // 监听URL变化 this.observeUrlChanges(); } /** * 生成大纲项 */ generateOutlineItems() { const outlineItems = this.outlineContainer.querySelector('.outline-items'); // 清空现有内容 while (outlineItems.firstChild) { outlineItems.removeChild(outlineItems.firstChild); } // 获取所有用户消息 - 使用Kimi特定的选择器 const userMessages = document.querySelectorAll('.segment-user'); if (userMessages.length === 0) { const emptyDiv = document.createElement('div'); emptyDiv.className = 'outline-empty'; emptyDiv.textContent = '暂无对话内容'; outlineItems.appendChild(emptyDiv); return; } userMessages.forEach((message, index) => { // 提取消息文本 - 适应Kimi的DOM结构 const messageText = message.querySelector('.user-content')?.textContent || ''; const title = this.extractQuestionTitle(messageText); const item = document.createElement('div'); item.className = 'outline-item'; item.dataset.index = index; item.dataset.messageId = `kimi-message-${index}`; // 创建图标 const iconSpan = document.createElement('span'); iconSpan.className = 'outline-item-icon'; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 24 24'); iconSvg.setAttribute('fill', 'none'); const iconPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); iconPath.setAttribute('d', 'M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z'); iconPath.setAttribute('stroke', 'currentColor'); iconPath.setAttribute('fill', 'currentColor'); iconSvg.appendChild(iconPath); iconSpan.appendChild(iconSvg); // 创建文本 const textSpan = document.createElement('span'); textSpan.className = 'outline-item-text'; textSpan.textContent = `${index + 1}. ${title}`; // 组装项目 item.appendChild(iconSpan); item.appendChild(textSpan); // 添加点击事件 item.addEventListener('click', () => this.handleItemClick(item, message)); outlineItems.appendChild(item); }); // 检查是否有可见的消息,并高亮对应的大纲项 this.highlightVisibleItem(); } /** * 监听新消息 - Kimi版本 */ observeNewMessages() { const observer = new MutationObserver((mutations) => { let shouldUpdate = false; mutations.forEach(mutation => { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE && (node.classList.contains('chat-content-item') || node.querySelector('.segment-user'))) { shouldUpdate = true; break; } } } }); if (shouldUpdate) { setTimeout(() => this.generateOutlineItems(), 50); } }); // 监听整个聊天容器 const chatContainer = document.querySelector('.chat-content-list'); if (chatContainer) { observer.observe(chatContainer, { childList: true, subtree: true }); } // 监听URL变化,因为切换对话可能会改变URL this.observeUrlChanges(); } /** * 监听URL变化 */ observeUrlChanges() { // 使用setInterval定期检查URL是否变化 setInterval(() => { const currentUrl = window.location.href; if (this.currentUrl !== currentUrl) { console.log('URL changed, reinitializing outline generator'); this.currentUrl = currentUrl; // 清空现有大纲 const outlineItems = this.outlineContainer.querySelector('.outline-items'); while (outlineItems && outlineItems.firstChild) { outlineItems.removeChild(outlineItems.firstChild); } // 等待新页面加载完成后重新生成大纲 setTimeout(() => this.generateOutlineItems(), 500); } }, 500); // 每秒检查一次 } /** * 高亮当前可见的消息对应的大纲项 - Kimi版本 */ highlightVisibleItem() { const userMessages = document.querySelectorAll('.segment-user'); if (!userMessages.length) return; // 找到当前视口中最靠近顶部的消息 let closestMessage = null; let closestDistance = Infinity; const viewportHeight = window.innerHeight; const viewportMiddle = viewportHeight / 2; userMessages.forEach(message => { const rect = message.getBoundingClientRect(); // 计算消息中心点到视口中心的距离 const distance = Math.abs((rect.top + rect.bottom) / 2 - viewportMiddle); // 如果消息在视口内且距离更近 if (rect.top < viewportHeight && rect.bottom > 0 && distance < closestDistance) { closestDistance = distance; closestMessage = message; } }); if (closestMessage) { // 找到对应的大纲项并高亮 const index = Array.from(userMessages).indexOf(closestMessage); const outlineItem = this.outlineContainer.querySelector(`.outline-item[data-index="${index}"]`); if (outlineItem) { this.highlightItem(outlineItem); } } } } /** * 插件管理器类 - 用于管理多个插件 */ class PluginManager { constructor() { this.plugins = []; } /** * 注册(不可用)插件 * @param {Object} plugin 插件实例 */ register(plugin) { this.plugins.push(plugin); return this; } /** * 初始化所有插件 */ initAll() { this.plugins.forEach(plugin => { if (typeof plugin.init === 'function') { setTimeout(() => plugin.init(), 100); // 延迟启动,确保页面已加载 } }); } } /** * 创建并启动插件管理器 */ $(function () { // 添加延迟,确保所有DOM元素都已经渲染完成 // 创建插件管理器 const pluginManager = new PluginManager(); // 定义支持的网站及其对应的生成器类 const generatorMap = { 'chatgpt.com': ChatGPTOutlineGenerator, 'chat.deepseek.com': DeepSeekOutlineGenerator, 'gemini.google.com': GeminiOutlineGenerator, 'ying.baichuan-ai.com': BaichuanOutlineGenerator, 'kimi.moonshot.cn': KimiOutlineGenerator }; // 获取当前网站域名 const currentUrl = window.location.href; // 遍历支持的网站,找到匹配的生成器并注册(不可用) for (const [domain, GeneratorClass] of Object.entries(generatorMap)) { if (currentUrl.includes(domain)) { // 注册(不可用)对应的大纲生成器插件 pluginManager.register(new GeneratorClass()); break; // 找到匹配的生成器后退出循环 } } // 初始化所有插件 pluginManager.initAll(); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址