// ==UserScript== // @name Genspark 快捷导航 // @namespace http://tampermonkey.net/ // @version 1.6 // @description 为 genspark.ai 对话页面添加快捷导航功能和代码折叠功能,支持上下箭头快速跳转消息,双击代码块展开/收起,让用户能够快速跳转到任何问题和回答 // @author schweigen // @license MIT // @match https://www.genspark.ai/agents* // @match https://genspark.ai/agents* // @grant none // @run-at document-start // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 配置选项 const CONFIG = { maxTitleLength: 50, refreshInterval: 2000, animationDuration: 300, codeCollapseLine: 5 // 超过此行数的代码块将被折叠 }; // 等待页面完全加载 function waitForElement(selector, timeout = 10000) { return new Promise((resolve, reject) => { const startTime = Date.now(); function check() { const element = document.querySelector(selector); if (element) { resolve(element); return; } if (Date.now() - startTime > timeout) { reject(new Error(`Element ${selector} not found within ${timeout}ms`)); return; } setTimeout(check, 100); } check(); }); } // 创建导航面板 function createNavigationPanel() { const nav = document.createElement('div'); nav.id = 'genspark-quicknav'; nav.innerHTML = `
快捷导航
`; // 创建收起状态的小方块 const miniNav = document.createElement('div'); miniNav.id = 'genspark-quicknav-mini'; miniNav.innerHTML = `
导航
`; miniNav.style.display = 'none'; // 创建上下导航按钮 const prevBtn = document.createElement('div'); prevBtn.id = 'genspark-quicknav-prev'; prevBtn.innerHTML = `
`; const nextBtn = document.createElement('div'); nextBtn.id = 'genspark-quicknav-next'; nextBtn.innerHTML = `
`; // 添加样式 const style = document.createElement('style'); style.textContent = ` #genspark-quicknav { position: fixed; top: 45%; left: 16px; transform: translateY(-50%); width: 320px; max-height: 70vh; background: #ffffff; border: 1px solid #d1d5db; border-radius: 12px; box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; font-size: 14px; overflow: hidden; transition: all ${CONFIG.animationDuration}ms cubic-bezier(0.4, 0, 0.2, 1); backdrop-filter: blur(8px); border: 1px solid rgba(255, 255, 255, 0.2); } #genspark-quicknav.collapsed { display: none; } #genspark-quicknav.hidden { display: none; } #genspark-quicknav-mini { position: fixed; top: 45%; left: 16px; transform: translateY(-50%); width: 48px; height: 48px; background: linear-gradient(135deg, #10b981 0%, #34d399 100%); border: none; border-radius: 50%; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1); z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; cursor: pointer; transition: all ${CONFIG.animationDuration}ms cubic-bezier(0.4, 0, 0.2, 1); display: flex; align-items: center; justify-content: center; } #genspark-quicknav-mini:hover { transform: translateY(-50%) scale(1.1); box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2), 0 3px 6px rgba(0, 0, 0, 0.15); } #genspark-quicknav-mini.hidden { display: none; } #genspark-quicknav-prev, #genspark-quicknav-next { position: fixed; left: 16px; width: 32px; height: 32px; background: linear-gradient(135deg, #10b981 0%, #34d399 100%); border: none; border-radius: 50%; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08); z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; cursor: pointer; transition: all ${CONFIG.animationDuration}ms cubic-bezier(0.4, 0, 0.2, 1); display: flex; align-items: center; justify-content: center; opacity: 0.8; } #genspark-quicknav-prev { top: calc(45% - 60px); transform: translateY(-50%); } #genspark-quicknav-next { top: calc(45% + 60px); transform: translateY(-50%); } #genspark-quicknav-prev:hover, #genspark-quicknav-next:hover { transform: translateY(-50%) scale(1.1); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.1); opacity: 1; } #genspark-quicknav-prev.hidden, #genspark-quicknav-next.hidden { display: none; } .quicknav-arrow-content { color: #ffffff; font-size: 14px; font-weight: 600; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; user-select: none; } .quicknav-mini-content { color: #ffffff; font-size: 12px; font-weight: 600; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; } .quicknav-header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); border-bottom: 1px solid #e5e7eb; border-radius: 12px 12px 0 0; } .quicknav-title { font-weight: 600; font-size: 16px; color: #1f2937; margin: 0; } .quicknav-controls { display: flex; gap: 8px; align-items: center; } .quicknav-controls button { background: rgba(255, 255, 255, 0.8); border: 1px solid rgba(0, 0, 0, 0.1); padding: 6px 8px; border-radius: 6px; cursor: pointer; font-size: 12px; color: #4b5563; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); display: flex; align-items: center; justify-content: center; min-width: 28px; height: 28px; } .quicknav-controls button:hover { background: #ffffff; color: #1f2937; transform: translateY(-1px); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .quicknav-content { max-height: calc(70vh - 80px); overflow-y: auto; background: #ffffff; border-radius: 0 0 12px 12px; } .quicknav-list { padding: 8px 0; } .quicknav-item { display: flex; align-items: center; padding: 12px 20px; cursor: pointer; border-left: 4px solid transparent; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); line-height: 1.4; margin: 0 8px; border-radius: 8px; position: relative; } .quicknav-item:hover { background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%); transform: translateX(4px); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .quicknav-item.user { border-left-color: #10b981; } .quicknav-item.user:hover { background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%); border-left-color: #059669; } .quicknav-item.assistant { border-left-color: #3b82f6; } .quicknav-item.assistant:hover { background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%); border-left-color: #2563eb; } .quicknav-item-icon { width: 16px; height: 16px; margin-right: 12px; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 600; color: #ffffff; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .quicknav-item.user .quicknav-item-icon { background: linear-gradient(135deg, #10b981 0%, #059669 100%); } .quicknav-item.assistant .quicknav-item-icon { background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); } .quicknav-item-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #374151; font-size: 14px; font-weight: 500; line-height: 1.5; } .quicknav-item-index { font-size: 12px; color: #6b7280; margin-left: 8px; flex-shrink: 0; padding: 2px 6px; background: rgba(0, 0, 0, 0.05); border-radius: 4px; font-weight: 500; } .quicknav-empty { padding: 40px 20px; text-align: center; color: #9ca3af; font-size: 14px; font-style: italic; } /* 滚动条样式 */ .quicknav-content::-webkit-scrollbar { width: 6px; } .quicknav-content::-webkit-scrollbar-track { background: #f8f9fa; border-radius: 3px; } .quicknav-content::-webkit-scrollbar-thumb { background: linear-gradient(135deg, #d1d5db 0%, #9ca3af 100%); border-radius: 3px; transition: background 0.2s; } .quicknav-content::-webkit-scrollbar-thumb:hover { background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%); } `; document.head.appendChild(style); document.body.appendChild(nav); document.body.appendChild(miniNav); document.body.appendChild(prevBtn); document.body.appendChild(nextBtn); return { nav, miniNav, prevBtn, nextBtn }; } // 提取消息文本 function extractMessageText(element) { const contentElement = element.querySelector('.content'); if (!contentElement) return ''; // 尝试从code标签中提取(用户消息) const codeElement = contentElement.querySelector('code'); if (codeElement) { return codeElement.textContent.trim(); } // 尝试从markdown-viewer中提取(AI消息) const markdownElement = contentElement.querySelector('.markdown-viewer'); if (markdownElement) { return markdownElement.textContent.trim(); } // 备用方案 return contentElement.textContent.trim(); } // 扫描并更新导航列表 function updateNavigationList() { const navList = document.querySelector('.quicknav-list'); if (!navList) return; const messages = document.querySelectorAll('.conversation-statement'); if (messages.length === 0) { navList.innerHTML = '
暂无对话消息
'; return; } navList.innerHTML = ''; let userIndex = 1; let assistantIndex = 1; messages.forEach((message, index) => { const isUser = message.classList.contains('user'); const isAssistant = message.classList.contains('assistant'); if (!isUser && !isAssistant) return; const text = extractMessageText(message); if (!text) return; const item = document.createElement('div'); item.className = `quicknav-item ${isUser ? 'user' : 'assistant'}`; const truncatedText = text.length > CONFIG.maxTitleLength ? text.substring(0, CONFIG.maxTitleLength) + '...' : text; const displayIndex = isUser ? userIndex++ : assistantIndex++; const prefix = isUser ? '问' : '答'; item.innerHTML = `
${isUser ? 'U' : 'A'}
${truncatedText}
${prefix}${displayIndex}
`; // 添加点击事件 item.addEventListener('click', () => { message.scrollIntoView({ behavior: 'smooth', block: 'center' }); // 高亮显示目标消息 message.style.transition = 'background-color 0.5s ease'; message.style.backgroundColor = '#fff3cd'; setTimeout(() => { message.style.backgroundColor = ''; }, 2000); }); navList.appendChild(item); }); } // 消息导航功能 function getCurrentMessageIndex() { const messages = document.querySelectorAll('.conversation-statement'); const viewportHeight = window.innerHeight; const viewportCenter = window.scrollY + viewportHeight / 2; let closestIndex = 0; let closestDistance = Infinity; messages.forEach((message, index) => { const messageRect = message.getBoundingClientRect(); const messageCenter = window.scrollY + messageRect.top + messageRect.height / 2; const distance = Math.abs(messageCenter - viewportCenter); if (distance < closestDistance) { closestDistance = distance; closestIndex = index; } }); return closestIndex; } function navigateToMessage(direction) { const messages = document.querySelectorAll('.conversation-statement'); if (messages.length === 0) return; const currentIndex = getCurrentMessageIndex(); let targetIndex; if (direction === 'prev') { targetIndex = Math.max(0, currentIndex - 1); } else { targetIndex = Math.min(messages.length - 1, currentIndex + 1); } const targetMessage = messages[targetIndex]; if (targetMessage) { targetMessage.scrollIntoView({ behavior: 'smooth', block: 'center' }); // 高亮显示目标消息 targetMessage.style.transition = 'background-color 0.5s ease'; targetMessage.style.backgroundColor = '#fff3cd'; setTimeout(() => { targetMessage.style.backgroundColor = ''; }, 2000); } } // 初始化导航面板 function initNavigationPanel() { const { nav, miniNav, prevBtn, nextBtn } = createNavigationPanel(); let isCollapsed = true; // 默认折叠状态 let isHidden = false; // 设置初始状态 nav.classList.add('collapsed'); miniNav.style.display = 'block'; // 绑定控制按钮事件 const refreshBtn = nav.querySelector('.quicknav-refresh'); const toggleBtn = nav.querySelector('.quicknav-toggle'); const closeBtn = nav.querySelector('.quicknav-close'); refreshBtn.addEventListener('click', updateNavigationList); toggleBtn.addEventListener('click', () => { isCollapsed = !isCollapsed; nav.classList.toggle('collapsed', isCollapsed); miniNav.style.display = isCollapsed ? 'block' : 'none'; toggleBtn.textContent = isCollapsed ? '+' : '−'; }); closeBtn.addEventListener('click', () => { isHidden = !isHidden; nav.classList.toggle('hidden', isHidden); miniNav.classList.toggle('hidden', isHidden); prevBtn.classList.toggle('hidden', isHidden); nextBtn.classList.toggle('hidden', isHidden); }); // 点击小方块展开导航 miniNav.addEventListener('click', () => { isCollapsed = false; nav.classList.remove('collapsed'); miniNav.style.display = 'none'; toggleBtn.textContent = '−'; }); // 上下导航按钮事件 prevBtn.addEventListener('click', () => { navigateToMessage('prev'); }); nextBtn.addEventListener('click', () => { navigateToMessage('next'); }); // 定期刷新导航列表 setInterval(updateNavigationList, CONFIG.refreshInterval); // 初始更新 updateNavigationList(); } // 代码折叠功能 function initCodeCollapse() { // 添加代码折叠的样式 const codeStyle = document.createElement('style'); codeStyle.textContent = ` .code-collapse-wrapper { position: relative; } .code-collapse-toggle { position: absolute; right: 32px; top: 8px; background: rgba(255, 255, 255, 0.9); border: 1px solid #d1d5db; border-radius: 4px; padding: 4px 8px; cursor: pointer; font-size: 12px; color: #374151; z-index: 1000; transition: all 0.2s ease; user-select: none; } .code-collapse-toggle:hover { background: #f3f4f6; border-color: #9ca3af; } .code-collapsed { max-height: ${CONFIG.codeCollapseLine * 1.5}em; overflow: hidden; position: relative; cursor: pointer; } .code-collapsed::after { content: ''; position: absolute; bottom: 0; left: 0; right: 0; height: 3em; background: linear-gradient(transparent, rgba(255, 255, 255, 0.9)); pointer-events: none; } .code-collapsed:hover::after { background: linear-gradient(transparent, rgba(240, 248, 255, 0.95)); } .code-expanded { max-height: none; overflow: visible; cursor: pointer; } .code-expanded::after { display: none; } `; document.head.appendChild(codeStyle); // 处理代码块折叠 function processCodeBlocks() { const codeBlocks = document.querySelectorAll('pre code.hljs'); codeBlocks.forEach(codeBlock => { // 避免重复处理 if (codeBlock.closest('.code-collapse-wrapper')) return; const lines = codeBlock.textContent.split('\n').filter(line => line.trim() !== ''); // 只对超过指定行数的代码块添加折叠功能 if (lines.length > CONFIG.codeCollapseLine) { const preElement = codeBlock.parentElement; // 创建包装器 const wrapper = document.createElement('div'); wrapper.className = 'code-collapse-wrapper'; // 创建切换按钮 const toggleBtn = document.createElement('div'); toggleBtn.className = 'code-collapse-toggle'; toggleBtn.textContent = '收起'; // 包装原始的 pre 元素 preElement.parentNode.insertBefore(wrapper, preElement); wrapper.appendChild(preElement); wrapper.appendChild(toggleBtn); // 初始状态为折叠 codeBlock.classList.add('code-collapsed'); // 切换功能函数 function toggleCodeBlock() { const isCollapsed = codeBlock.classList.contains('code-collapsed'); if (isCollapsed) { codeBlock.classList.remove('code-collapsed'); codeBlock.classList.add('code-expanded'); toggleBtn.textContent = '收起'; } else { codeBlock.classList.remove('code-expanded'); codeBlock.classList.add('code-collapsed'); toggleBtn.textContent = '展开'; } } // 按钮点击事件 toggleBtn.addEventListener('click', toggleCodeBlock); // 双击代码块展开/收起 codeBlock.addEventListener('dblclick', (e) => { // 防止双击时选中文本 e.preventDefault(); toggleCodeBlock(); }); // 初始设置按钮文本 toggleBtn.textContent = '展开'; } }); } // 初始处理 processCodeBlocks(); // 监听DOM变化,处理动态加载的代码块 const observer = new MutationObserver(mutations => { let shouldProcess = false; mutations.forEach(mutation => { if (mutation.type === 'childList') { mutation.addedNodes.forEach(node => { if (node.nodeType === 1 && (node.querySelector && node.querySelector('code.hljs') || node.matches && node.matches('code.hljs'))) { shouldProcess = true; } }); } }); if (shouldProcess) { setTimeout(processCodeBlocks, 100); } }); observer.observe(document.body, { childList: true, subtree: true }); } // 主函数 async function main() { try { // 等待对话容器加载 await waitForElement('.conversation-wrapper'); // 稍微延迟以确保内容完全加载 setTimeout(() => { initNavigationPanel(); initCodeCollapse(); // 初始化代码折叠功能 }, 1000); } catch (error) { console.error('Genspark QuickNav: 初始化失败', error); } } // 页面加载完成后启动 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', main); } else { main(); } })();