Instantly share code, notes, and snippets.
Last active
November 23, 2025 03:29
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save KnIfER/bd4d3c3e7792ae320db81c644c60e9ba to your computer and use it in GitHub Desktop.
豆包专属 Grok 风格右侧消息导航栏
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name doubao_navigator | |
| // @namespace http://tampermonkey.net/ | |
| // @version 1.1 | |
| // @author Grok and you | |
| // @match https://www.doubao.com/* | |
| // @icon https://www.google.com/s2/favicons?sz=64&domain=doubao.com | |
| // @grant unsafeWindow | |
| // ==/UserScript== | |
| var w0 = window; | |
| var win = w0.unsafeWindow || w0; | |
| var w = win; | |
| var d = document; | |
| win._bank ||= {}; | |
| win._bank.fet ||= w0.GM_xmlhttpRequest; | |
| win.gm_open ||= w0.GM_openInTab; | |
| var bank = win._bank; | |
| if(bank.unreg) bank.unreg() | |
| var unregs = []; | |
| bank.unreg = _=>{ // uninstall | |
| for(var i=0;i<unregs.length;i++) { | |
| try{unregs[i]()} catch(e){de("wtf", i, e, unregs[i])} | |
| } | |
| return 0; | |
| }; | |
| function addEvent(a, b, d, c) { | |
| if(!d) d = win; | |
| d.addEventListener(a, b, c); | |
| unregs.push(function(){ d.removeEventListener(a, b, c)} ); | |
| } | |
| function delEvent(a, b, c, d) { | |
| if(!d) d = win; | |
| d.removeEventListener(a, b, c); | |
| } | |
| w.doc = document; | |
| w.de = console.log; | |
| w.log = w.debug = de; | |
| function craft(t, p, c, s) { | |
| t = d.createElement(t||'DIV'); | |
| if(c) t.className=c; | |
| if(s) t.style=s; | |
| if(p)p.appendChild(t); | |
| win.lastCel = t; | |
| return t; | |
| } | |
| win.craft = craft; | |
| // ==================== 样式(无动画 + 左侧 + 可拖动 + 折叠按钮) ==================== | |
| STYLER = craft('style', document.head) | |
| STYLER.textContent = ` | |
| /* 左侧可拖动导航栏 */ | |
| #grok-message-sidebar { | |
| position: fixed; | |
| left: 20px; /* 初始靠左 */ | |
| top: 100px; /* 初始顶部位置 */ | |
| width: 280px; | |
| min-height: 60px; | |
| max-height: calc(100vh - 140px); | |
| background: rgba(255, 255, 255, 0.92); | |
| backdrop-filter: blur(12px); | |
| border: 1px solid rgba(0,0,0,0.1); | |
| border-radius: 16px; | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.12); | |
| z-index: 9999; | |
| overflow: hidden; | |
| padding: 0; | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; | |
| user-select: none; | |
| resize: both; /* 允许用户手动调整大小 */ | |
| } | |
| /* 拖动标题栏 */ | |
| #grok-sidebar-header { | |
| padding: 12px 16px; | |
| background: rgba(240, 240, 246, 0.8); | |
| border-bottom: 1px solid #eee; | |
| cursor: move; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| font-weight: 600; | |
| font-size: 14px; | |
| color: #1a1a1a; | |
| position: sticky; | |
| top: 0; | |
| z-index: 10; | |
| } | |
| /* 折叠按钮 */ | |
| #grok-sidebar-toggle { | |
| width: 24px; | |
| height: 24px; | |
| border: none; | |
| background: none; | |
| font-size: 18px; | |
| cursor: pointer; | |
| opacity: 0.6; | |
| } | |
| #grok-sidebar-toggle:hover { opacity: 1; } | |
| /* 内容区域 */ | |
| #grok-sidebar-content { | |
| overflow-y: auto; | |
| overflow-x: hidden; | |
| padding: 12px 0; | |
| max-height: calc(100vh - 200px); | |
| background: #3b514b; | |
| } | |
| #grok-sidebar-content.collapsed { | |
| display: none; | |
| } | |
| /* 导航项(去掉所有 transition) */ | |
| .grok-sidebar-item { | |
| padding: 8px 12px; | |
| cursor: pointer; | |
| border-radius: 8px; | |
| margin: 2px 8px; | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 8px; | |
| font-size: 13px; | |
| line-height: 1.4; | |
| max-height: 72px; | |
| overflow: hidden; | |
| } | |
| .grok-sidebar-item:hover { | |
| background: #f5f5f5; | |
| } | |
| .grok-sidebar-item.active { | |
| background: #e6f3ff; | |
| border: 1px solid #99d1ff; | |
| font-weight: 500; | |
| } | |
| .grok-sidebar-item.user { color: #d12f2f; } | |
| .grok-sidebar-item.ai { color: #0066cc; } | |
| .grok-sidebar-icon { | |
| font-size: 16px; | |
| flex-shrink: 0; | |
| margin-top: 1px; | |
| } | |
| /* 滚动条 */ | |
| #grok-sidebar-content::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| #grok-sidebar-content::-webkit-scrollbar-thumb { | |
| background: rgba(0,0,0,0.2); | |
| border-radius: 3px; | |
| } | |
| .grok-code-entry { | |
| font-size: 12px; | |
| color: #0066cc; | |
| padding: 4px 0 4px 20px; | |
| position: relative; | |
| } | |
| .grok-code-entry::before { | |
| content: "▶"; | |
| position: absolute; | |
| left: 8px; | |
| font-size: 10px; | |
| top: 6px; | |
| } | |
| .grok-code-entry:hover { | |
| color: #0050aa; | |
| text-decoration: underline; | |
| } | |
| `; | |
| unregs.push(_=>STYLER.remove()) | |
| // ==================== 创建侧边栏结构 ==================== | |
| var sidebar = document.createElement('div'); | |
| sidebar.id = 'grok-message-sidebar'; | |
| var header = document.createElement('div'); | |
| header.id = 'grok-sidebar-header'; | |
| header.innerHTML = ` | |
| <span>对话导航(<span id="grok-msg-count">0</span> 轮)</span> | |
| <button id="grok-sidebar-toggle" title="折叠/展开">−</button> | |
| `; | |
| var content = document.createElement('div'); | |
| content.id = 'grok-sidebar-content'; | |
| sidebar.appendChild(header); | |
| sidebar.appendChild(content); | |
| document.body.appendChild(sidebar); | |
| unregs.push(_=>sidebar.remove()) | |
| // ==================== 拖动功能 ==================== | |
| var isDragging = false; | |
| var currentX, currentY, initialX, initialY; | |
| addEvent('mousedown', (e) => { | |
| if (e.target.tagName === 'BUTTON') return; | |
| isDragging = true; | |
| initialX = e.clientX - currentX; | |
| initialY = e.clientY - currentY; | |
| }, header); | |
| addEvent('mousemove', (e) => { | |
| if (isDragging) { | |
| e.preventDefault(); | |
| currentX = e.clientX - initialX; | |
| currentY = e.clientY - initialY; | |
| sidebar.style.left = currentX + 'px'; | |
| sidebar.style.top = currentY + 'px'; | |
| sidebar.style.right = 'auto'; | |
| sidebar.style.bottom = 'auto'; | |
| } | |
| }, d); | |
| addEvent('mouseup', () => { | |
| if (isDragging) { | |
| isDragging = false; | |
| // 保存位置(可选) | |
| } | |
| }, d); | |
| // 初始化拖动坐标 | |
| var rect = sidebar.getBoundingClientRect(); | |
| currentX = rect.left; | |
| currentY = rect.top; | |
| // ==================== 折叠/展开 ==================== | |
| var toggleBtn = header.querySelector('#grok-sidebar-toggle'); | |
| toggleBtn.addEventListener('click', () => { | |
| content.classList.toggle('collapsed'); | |
| toggleBtn.textContent = content.classList.contains('collapsed') ? '+' : '−'; | |
| if (!content.classList.contains('collapsed')) { | |
| buildSidebar(); // 展开时刷新一次 | |
| } | |
| }); | |
| // ==================== 原有逻辑(基本不变,仅微调选择器) ==================== | |
| USER_ICON = '>'; // 'User' | |
| AI_ICON = 'AI'; | |
| function getMessageBlocks() { | |
| return Array.from(document.querySelectorAll('[data-testid="message-block-container"]')); | |
| } | |
| function truncate(text, len = 60) { | |
| if (!text) return '(空消息)'; | |
| text = text.replace(/\s+/g, ' ').trim(); | |
| return text.length > len ? text.slice(0, len) + '…' : text; | |
| } | |
| function buildSidebar() { | |
| console.log('buildSidebar::'); | |
| var blocks = getMessageBlocks(); | |
| var countEl = document.getElementById('grok-msg-count'); | |
| var roundCount = Math.floor(blocks.length / 2); | |
| countEl.textContent = roundCount; | |
| var frag = document.createDocumentFragment(); | |
| blocks.forEach((block, idx) => { // 轮回 | |
| var isUser = block.querySelector('[data-testid="send_message"]'); | |
| var contentDiv = block.querySelector('[data-testid="message_text_content"]'); | |
| if (!contentDiv) return; | |
| var text = contentDiv.innerText || ''; | |
| if (text.trim() === '') return; | |
| var item = document.createElement('div'); | |
| item.className = `grok-sidebar-item ${isUser ? 'user' : 'ai'}`; | |
| item.dataset.index = idx; | |
| if(isUser) | |
| item.innerHTML = ` | |
| <span class="grok-sidebar-icon">${isUser ? USER_ICON : AI_ICON}</span> | |
| <div style="flex:1;overflow:hidden;;margin:auto;"> | |
| <div style="color:#fff;font-size:12px">${truncate(text)}</div> | |
| </div> | |
| `; | |
| else | |
| item.innerHTML = ` | |
| <span class="grok-sidebar-icon">${isUser ? USER_ICON : AI_ICON}</span> | |
| <div style="flex:1;overflow:hidden;"> | |
| <div style="font-weight:500;margin-bottom:2px;">${isUser ? '你' : '豆包'}</div> | |
| <div style="color:#666;font-size:12px;">${truncate(text)}</div> | |
| </div> | |
| `; | |
| frag.appendChild(item); | |
| if(!isUser) { | |
| // 查找 AI 回复里所有的代码块 | |
| const codeBlocks = block.querySelectorAll('pre code'); | |
| codeBlocks.forEach((codeEl, codeIdx) => { | |
| const codeEntry = document.createElement('div'); | |
| codeEntry.className = 'grok-code-entry'; | |
| var title = block.querySelectorAll('[class*="title-"]')[0]; | |
| // console.log('title::', title); | |
| codeEntry.textContent = (title?.innerText||'')+` 代码 `; // ${codeIdx + 1} | |
| codeEntry.onclick = e => { | |
| codeEl.scrollIntoView(); | |
| }; | |
| frag.appendChild(codeEntry); | |
| //frag.appendChild(codeEntry.cloneNode(1)); | |
| }); | |
| } | |
| item.onclick = () => { | |
| block.scrollIntoView(); | |
| // block.scrollIntoView({ behavior: 'smooth', block: 'center' }); | |
| document.querySelectorAll('.grok-sidebar-item.active').forEach(el => el.classList.remove('active')); | |
| item.classList.add('active'); | |
| block.style.background = isUser ? 'rgba(255,240,240,0.6)' : 'rgba(230,243,255,0.6)'; | |
| setTimeout(() => { block.style.background = ''; }, 2000); | |
| }; | |
| }); | |
| content.innerHTML = ''; | |
| content.appendChild(frag); | |
| // 自动高亮当前可见消息 | |
| // setTimeout(highlightCurrent, 300); | |
| } | |
| function highlightCurrent() { | |
| var blocks = getMessageBlocks(); | |
| var closest = null; | |
| var minDist = Infinity; | |
| blocks.forEach((block, idx) => { | |
| var rect = block.getBoundingClientRect(); | |
| var dist = Math.abs(rect.top - window.innerHeight / 2); | |
| if (dist < minDist) { | |
| minDist = dist; | |
| closest = idx; | |
| } | |
| }); | |
| document.querySelectorAll('.grok-sidebar-item.active').forEach(el => el.classList.remove('active')); | |
| if (closest !== null) { | |
| var item = content.querySelector(`.grok-sidebar-item[data-index="${closest}"]`); | |
| if (item) item.classList.add('active'); | |
| } | |
| } | |
| // 初始构建 | |
| setTimeout(() => { | |
| buildSidebar(); | |
| }, 1500); | |
| var tm_rebuild; | |
| // 动态监听新消息 | |
| var observer = new MutationObserver((mutations) => { | |
| if (mutations.some(m => m.addedNodes.length > 0)) { | |
| if (!content.classList.contains('collapsed')) { | |
| clearTimeout(tm_rebuild); | |
| tm_rebuild = setTimeout(buildSidebar, 800); | |
| } | |
| } | |
| }); | |
| observer = new MutationObserver(mutations => { | |
| let hasAddedNodes = false; | |
| for (let i = 0; i < mutations.length; i++) { | |
| if (mutations[i].addedNodes.length) { | |
| console.log('hasAddedNodes::', mutations[i].addedNodes); | |
| hasAddedNodes = true | |
| for (let index = 0; index < mutations[i].addedNodes.length; index++) { | |
| const element = mutations[i].addedNodes[index]; | |
| console.log('element.firstChild?.classList::', element.firstChild?.classList); | |
| if(element.firstChild?.classList?.contains("cursor-pointer")) { | |
| hasAddedNodes = false | |
| break; | |
| } | |
| } | |
| if(hasAddedNodes) | |
| break; | |
| else | |
| onScroll() | |
| } | |
| } | |
| // 有新增节点且内容未折叠时执行逻辑 | |
| if (hasAddedNodes && !content.classList.contains('collapsed')) { | |
| clearTimeout(tm_rebuild); | |
| tm_rebuild = setTimeout(buildSidebar, 800); | |
| } | |
| }); | |
| unregs.push(_=>observer.disconnect()) | |
| win.buildSidebar = buildSidebar; | |
| var messageList; | |
| function obz() { | |
| messageList = document.querySelector('[data-testid="message-list"]'); | |
| console.log('messageList::', messageList); | |
| if(messageList && !messageList._obz) { | |
| messageList._obz = 1; | |
| observer.disconnect(); | |
| observer.observe(messageList, { childList: true, subtree: true }); | |
| } | |
| return !!messageList | |
| } | |
| function onScroll() { | |
| // requestAnimationFrame(highlightCurrent) | |
| // todo | |
| } | |
| // addEvent('scroll', e => { | |
| // console.log('scroll::', e.target); | |
| // clearTimeout(tm_rebuild); | |
| // tm_rebuild = setTimeout(buildSidebar, 800); | |
| // // requestAnimationFrame(highlightCurrent) | |
| // },d); | |
| var cc=0, foo; | |
| setTimeout(foo = () => { | |
| if(!obz() && cc++<100) | |
| setTimeout(foo) | |
| }, 250); | |
| console.log('%c Grok 风格左侧可拖动导航栏已注入(支持折叠、无动画)', 'color:#0066cc;font-size:18px;font-weight:bold'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment