最近经常使用豆包 AI 聊天,但当对话内容变多之后,查找历史问题会变得比较麻烦。

于是我用原生 JavaScript 写了一个简单的浏览器插件,给豆包页面增加了一个侧边导航栏。

可以快速定位历史对话内容。

目前实现的功能:

- 自动提取对话内容
- 侧边栏导航
- 点击快速定位
- 平滑滚动
- 悬停展开动画

const observer = new MutationObserver(() => this.refresh());

当豆包页面新增对话时,自动刷新导航栏。

conv.scrollIntoView({
    behavior: 'smooth',
    block: 'start'
});

点击导航项后,自动滚动到对应对话位置。

transition: width 0.25s ease;

实现导航栏悬停展开动画。

这是我的文件夹里面的三个文件

content.js

// content.js

// --- 辅助函数:从对话元素中提取文本 ---
function getQuestionText(conversation) {
    // 在这里,你要写上一步在开发者工具里找到的 class 名
    const userDiv = conversation.querySelector('.w-full'); 
    return userDiv ? userDiv.innerText.slice(0, 30) : `对话 ${Date.now()}`;
}

// --- 导航栏管理器 ---
class NavigationManager {
    constructor() {
        this.navContainer = null;
        this.conversations = [];
    }

    // 创建导航栏容器并添加到页面
    init() {
        if (this.navContainer) return;
        this.navContainer = document.createElement('div');
        this.navContainer.id = 'doubao-nav-sidebar';
        this.navContainer.innerHTML = `<h3>📑 对话导航</h3><div class="nav-list"></div>`;
        document.body.appendChild(this.navContainer);
    }

    // 刷新导航列表,映射到每轮对话
    refresh() {
        // 1. 获取所有对话条目(换成你实际找到的 class 名 .conversation-item)
        const conversationElements = document.querySelectorAll('.v_list_row');
        if (conversationElements.length === 0) return;
        if (conversationElements.length === this.conversations.length) return;

        // 2. 更新存储的对话列表
        this.conversations = Array.from(conversationElements);
        const navListDiv = this.navContainer?.querySelector('.nav-list');
        if (!navListDiv) return;

        // 3. 清空并重新生成导航链接
        navListDiv.innerHTML = '';
        this.conversations.forEach((conv, index) => {
            const question = getQuestionText(conv);
            const navItem = document.createElement('div');
            navItem.className = 'nav-item';
            navItem.textContent = `${index + 1}. ${question}`;

            // 点击时滚动到对应对话
            navItem.onclick = () => {
                conv.scrollIntoView({ behavior: 'smooth', block: 'start' });
                // 可选:高亮当前激活的导航项
                document.querySelectorAll('.nav-item').forEach(item => item.classList.remove('active'));
                navItem.classList.add('active');
            };
            navListDiv.appendChild(navItem);
        });
    }

    // 监听页面变化,自动更新导航
    observeConversations() {
        const observer = new MutationObserver(() => this.refresh());
        const targetNode = document.querySelector('.list_items'); // 换成你实际找到的容器 class 名
        if (targetNode) {
            observer.observe(targetNode, { childList: true, subtree: true });
        }
    }
}

// --- 启动扩展---
const nav = new NavigationManager();
nav.init();
setTimeout(() => { nav.refresh(); nav.observeConversations(); }, 1000);

styles.css

/* styles.css - 豆包导航栏:悬停展开,平时隐藏 */
#doubao-nav-sidebar {
    position: fixed;
    right: 20px;
    top: 50%;
    transform: translateY(-50%);
    width: 40px;
    /* 默认很窄 */
    max-height: 70vh;
    background: rgba(255, 255, 255, 0.9);
    backdrop-filter: blur(12px);
    border-radius: 24px;
    /* 圆润一点 */
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
    padding: 12px 0;
    z-index: 10000;
    display: flex;
    flex-direction: column;
    font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, sans-serif;
    border: 1px solid rgba(0, 0, 0, 0.08);
    transition: width 0.25s ease, background 0.2s ease;
    /* 平滑过渡 */
    overflow: hidden;
    /* 溢出内容隐藏,避免变形 */
}

/* 悬停时展开 */
#doubao-nav-sidebar:hover {
    width: 240px;
    background: rgba(255, 255, 255, 0.98);
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
}

/* 标题默认隐藏,悬停时显示 */
#doubao-nav-sidebar h3 {
    font-size: 14px;
    font-weight: 600;
    margin: 0 16px 12px 16px;
    padding-bottom: 8px;
    border-bottom: 1px solid rgba(0, 0, 0, 0.08);
    color: #1a1a1a;
    white-space: nowrap;
    opacity: 0;
    /* 默认透明 */
    transition: opacity 0.2s ease 0.05s;
}

#doubao-nav-sidebar:hover h3 {
    opacity: 1;
    /* 悬停时出现标题 */
}

/* 导航列表默认隐藏,悬停时显示滚动条 */
#doubao-nav-sidebar .nav-list {
    overflow-y: auto;
    flex: 1;
    padding: 0 8px;
    opacity: 0;
    transition: opacity 0.2s ease 0.05s;
}

#doubao-nav-sidebar:hover .nav-list {
    opacity: 1;
}

/* 导航项目样式保持不变,但为了悬停窄条时显示一个简单提示,可以加个伪元素 */
#doubao-nav-sidebar::before {
    content: "📑";
    font-size: 20px;
    text-align: center;
    display: block;
    line-height: 1;
    transition: opacity 0.2s;
}

/* 悬停时隐藏这个简单的图标(因为标题会显示出来) */
#doubao-nav-sidebar:hover::before {
    opacity: 0;
    pointer-events: none;
}

/* 调整导航项目的基础样式(确保窄条下不显示文字) */
#doubao-nav-sidebar .nav-item {
    padding: 8px 12px;
    margin: 4px 0;
    border-radius: 12px;
    font-size: 13px;
    cursor: pointer;
    color: #333;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: background 0.15s, color 0.15s;
    opacity: 0;
    /* 默认不可见 */
    transform: translateX(10px);
    /* 轻微偏移,增加动画感 */
    transition: opacity 0.2s, transform 0.2s;
}

#doubao-nav-sidebar:hover .nav-item {
    opacity: 1;
    transform: translateX(0);
}

/* 悬停时每个项目依次显现(可选,更优雅) */
#doubao-nav-sidebar:hover .nav-item:nth-child(1) {
    transition-delay: 0.02s;
}

#doubao-nav-sidebar:hover .nav-item:nth-child(2) {
    transition-delay: 0.04s;
}

#doubao-nav-sidebar:hover .nav-item:nth-child(3) {
    transition-delay: 0.06s;
}

/* 你可以继续加到 10 左右,但浏览器会自动处理,没关系 */

/* 自定义滚动条不变 */
#doubao-nav-sidebar .nav-list::-webkit-scrollbar {
    width: 4px;
}

#doubao-nav-sidebar .nav-list::-webkit-scrollbar-track {
    background: transparent;
}

#doubao-nav-sidebar .nav-list::-webkit-scrollbar-thumb {
    background: rgba(0, 0, 0, 0.2);
    border-radius: 4px;
}

/* 可选:鼠标不在导航栏时,让整个导航栏更半透明 */
#doubao-nav-sidebar {
    background: rgba(255, 255, 255, 0.7);
}

#doubao-nav-sidebar:hover {
    background: rgba(255, 255, 255, 0.98);
}

/* 确保点击穿透问题?不,我们不需要点击穿透,因为窄条时也没东西可点,悬停后才可点 */

manifest.json

{
  "manifest_version": 3,
  "name": "豆包对话导航助手",
  "version": "1.0",
  "description": "在豆包的AI对话页面上添加一个侧边导航栏,方便快速定位历史问题。",
  "permissions": [
    "activeTab"
  ],
  "content_scripts": [
    {
      "matches": ["*://www.doubao.com/*"],
      "js": ["content.js"],
      "css": ["styles.css"],
      "run_at": "document_end"
    }
  ]
}

这是我第一次尝试开发浏览器插件。

当然也遇到了很多问题,

第一个问题

这个代码也不能保证一直可以用,今天我用的时候就出现了问题,就是我的导航栏直接清空了,刷新也不行,然后我发现是豆包他一定时间会更新一次,因为这个导航的本质是去找到那个问题模块,也就是类,class。这个class更新的时候也会变,所以这个代码并不可以一直使用,豆包更新的时候就要通过检查台再去找一下对应的class并修改。

第二个问题

我对于JS的学习还不够深入,我的理想模型是和deep seek一样,只有问题的导航,但是我的这个导航不仅有问题还有对应的答案,刚开始我是有点烦的,这并没有达到我的理想目标,但是我的技术有限,只能到这一步,就将就着用了,用了一段时间发现还挺好的,有时候用户的问题也是比较长的,从问题到答案也是很长的要去滑动鼠标,而我的这个插件可以直接到对应的答案,所以我现在挺喜欢用的。

最后

欢迎大家使用我的插件,并帮我分析,给我一些用后的感受,如果有更好用的这样类型的插件也可以给我分享一下,希望有大神看到后可以帮我看看怎么修改才能和deep seek一样,我的代码哪里有问题可以给我指出来,

虽然功能还比较简单,但已经实现了基本的对话导航功能。

后面准备继续增加:

- 自定义 AI 网站
- 深色模式
- 快捷键唤起
- 多平台支持

如果有建议欢迎交流。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐