Skip to content

Instantly share code, notes, and snippets.

@KnIfER
Last active November 23, 2025 03:29
Show Gist options
  • Select an option

  • Save KnIfER/bd4d3c3e7792ae320db81c644c60e9ba to your computer and use it in GitHub Desktop.

Select an option

Save KnIfER/bd4d3c3e7792ae320db81c644c60e9ba to your computer and use it in GitHub Desktop.
豆包专属 Grok 风格右侧消息导航栏
// ==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