AI 导航栏(Gemini & ChatGPT & deepseek)

🚀 三合一 AI 侧边导航栏。支持 Gemini、ChatGPT、DeepSeek。提供长对话索引、极速跳转与本地收藏功能。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         AI 导航栏(Gemini & ChatGPT & deepseek)
// @namespace    https://github.com/kuilei98/ai-conversation-sidebar
// @version      1.0.1.1205
// @description  🚀 三合一 AI 侧边导航栏。支持 Gemini、ChatGPT、DeepSeek。提供长对话索引、极速跳转与本地收藏功能。
// @author       kuilei98
// @match        https://gemini.google.com/*
// @match        https://chatgpt.com/*
// @match        https://chat.openai.com/*
// @match        https://chat.deepseek.com/*
// @grant        none
// @license      MIT
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // 平台配置
    const PLATFORMS = {
        'gemini': {
            name: 'Gemini',
            color: '#0b57d0',
            highlightColor: 'rgba(11, 87, 208, 0.2)',
            topOffset: '140px',
            selectors: [
                'user-query',
                '.user-query',
                '[data-test-id="user-query"]',
                'div.user-query-container'
            ],
            getText: (el) => el.innerText || el.textContent || ''
        },
        'chatgpt': {
            name: 'ChatGPT',
            color: '#10a37f',
            highlightColor: 'rgba(16, 163, 127, 0.2)',
            topOffset: '140px',
            selectors: [
                '[data-message-author-role="user"]',
                '.group\\/conversation-turn:has([data-message-author-role="user"])'
            ],
            getText: (el) => el.innerText || el.textContent || ''
        },
        'deepseek': {
            name: 'DeepSeek',
            color: '#4d8aff',
            highlightColor: 'rgba(77, 138, 255, 0.2)',
            topOffset: '100px',
            customQueryList: () => {
                const questions = [];
                document.querySelectorAll('.ds-message:has(> .ds-markdown)').forEach(answerMsg => {
                    const parentDiv = answerMsg.parentElement;
                    const prevSibling = parentDiv ? parentDiv.previousElementSibling : null;
                    if (prevSibling) {
                        const questionMsg = prevSibling.querySelector('.ds-message');
                        if (questionMsg) questions.push(questionMsg);
                    }
                });
                return questions;
            },
            getText: (el) => el.innerText || el.textContent || ''
        }
    };

    // 环境检测
    const host = window.location.hostname;
    let currentPlatform = null;
    let siteKey = '';

    if (host.includes('gemini.google')) {
        currentPlatform = PLATFORMS.gemini;
        siteKey = 'gemini';
    } else if (host.includes('chatgpt') || host.includes('openai')) {
        currentPlatform = PLATFORMS.chatgpt;
        siteKey = 'chatgpt';
    } else if (host.includes('deepseek')) {
        currentPlatform = PLATFORMS.deepseek;
        siteKey = 'deepseek';
    } else {
        return;
    }

    // 全局状态
    const NAV_CONTAINER_ID = 'ai-nav-container-universal';
    let currentChatId = '';
    let cachedStars = {};

    // 样式注入
    const cssStyles = `
        /* 导航容器 */
        #${NAV_CONTAINER_ID} {
            position: fixed;
            top: ${currentPlatform.topOffset};
            right: 15px;
            width: auto;
            max-height: 70vh;
            display: flex;
            flex-direction: column;
            gap: 4px;
            align-items: flex-end;
            z-index: 2147483647;
            pointer-events: none;
            overflow-y: auto;
            overflow-x: visible;
            padding-right: 2px;
            padding-bottom: 20px;
            scrollbar-width: none;
        }
        #${NAV_CONTAINER_ID}::-webkit-scrollbar { display: none; }

        /* 配色变量 */
        :root {
            --ai-bg: #ffffff;
            --ai-border: rgba(0,0,0,0.08);
            --ai-shadow: 0 2px 6px rgba(0,0,0,0.08);
            --ai-shadow-hover: 0 8px 20px rgba(0,0,0,0.12);
            --ai-text-primary: #1f1f1f;
            --ai-text-secondary: #444746;
            --ai-accent: ${currentPlatform.color};
            --ai-star-inactive: #c4c7c5;
            --ai-star-active: #fbbc04;
            --ai-star-bg: #fff8e1;
            --ai-star-text: #b06000;
        }

        @media (prefers-color-scheme: dark) {
            :root {
                --ai-bg: #1e1f20;
                --ai-border: rgba(255,255,255,0.1);
                --ai-shadow: 0 2px 6px rgba(0,0,0,0.4);
                --ai-shadow-hover: 0 8px 24px rgba(0,0,0,0.6);
                --ai-text-primary: #e3e3e3;
                --ai-text-secondary: #c4c7c5;
                --ai-star-inactive: #8e918f;
                --ai-star-active: #fbbc04;
                --ai-star-bg: #3f3a2c;
                --ai-star-text: #fdd663;
            }
        }
        ${siteKey === 'chatgpt' ? `@media (prefers-color-scheme: dark) { :root { --ai-bg: #212121; } }` : ''}

        /* 页面内索引样式 */
        [data-ai-index]::before {
            content: attr(data-ai-index);
            display: inline-block;
            font-family: Consolas, monospace;
            font-weight: bold;
            color: var(--ai-accent);
            margin-right: 10px;
            font-size: 1.1em;
            opacity: 1;
            user-select: none;
            vertical-align: middle;
        }

        /* 胶囊按钮样式 */
        .nav-capsule {
            pointer-events: auto;
            background-color: var(--ai-bg);
            border: 1px solid var(--ai-border);
            color: var(--ai-text-primary);
            width: 34px;
            height: 34px;
            border-radius: 5px 0 0 5px;
            padding: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            box-shadow: var(--ai-shadow);
            cursor: pointer;
            transition: width 0.25s cubic-bezier(0.2, 0, 0, 1), background-color 0.2s, box-shadow 0.2s;
            overflow: hidden;
            white-space: nowrap;
            position: relative;
        }

        .nav-capsule:hover {
            width: 280px;
            padding: 0 12px;
            justify-content: space-between;
            background-color: var(--ai-bg);
            box-shadow: var(--ai-shadow-hover);
            z-index: 10000;
            border-color: transparent;
            border-radius: 5px 0 0 5px;
        }

        .capsule-index { font-weight: 700; font-size: 13px; color: var(--ai-accent); text-align: center; min-width: auto; }
        .capsule-text { display: none; font-size: 13px; color: var(--ai-text-secondary); flex: 1; margin: 0 12px; overflow: hidden; text-overflow: ellipsis; text-align: left; }
        .nav-capsule:hover .capsule-text { display: block; animation: fadeIn 0.2s forwards; }

        .capsule-star { display: none; font-size: 16px; color: var(--ai-star-inactive); width: 18px; text-align: center; opacity: 0.4; transform: scale(0.9); transition: all 0.2s ease; }
        .nav-capsule:hover .capsule-star { display: block; }
        .capsule-star.unlocked { opacity: 1 !important; color: var(--ai-text-primary); transform: scale(1.1); }
        .capsule-star.denied { animation: shake 0.3s ease; }

        @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
        @keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-2px); } 75% { transform: translateX(2px); } }

        .nav-capsule.starred { background-color: var(--ai-star-bg); border-color: transparent; }
        .nav-capsule.starred .capsule-index { color: var(--ai-star-text); }
        .nav-capsule.starred .capsule-star { display: block !important; opacity: 1 !important; color: var(--ai-star-active) !important; transform: scale(1); }
    `;

    const styleEl = document.createElement('style');
    styleEl.textContent = cssStyles;
    document.head.appendChild(styleEl);


    // 功能逻辑循环
    setInterval(() => {
        try {
            updateChatId();
            ensureContainer();
        } catch (e) { console.error("AI Nav Error:", e); }
    }, 1000);

    const scanInterval = siteKey === 'deepseek' ? 2000 : 1500;
    setInterval(scanMessages, scanInterval);

    // 更新当前会话ID
    function updateChatId() {
        const newPath = window.location.pathname;
        if (newPath !== currentChatId) {
            currentChatId = newPath;
            const storageKey = `${siteKey}_stars_${currentChatId}`;
            cachedStars = JSON.parse(localStorage.getItem(storageKey) || '{}');
            const container = document.getElementById(NAV_CONTAINER_ID);
            if(container) container.replaceChildren();
            scanMessages(true);
        }
    }

    // 生成唯一Key
    function getMessageKey(text, occurrenceIndex) {
        if (!text) return "empty_node";
        return text.trim().substring(0, 50) + "_idx_" + occurrenceIndex;
    }

    // 收藏状态切换
    function toggleStar(key, event, starElement, isCurrentlyStarred) {
        if (!isCurrentlyStarred && !starElement.classList.contains('unlocked')) {
            starElement.classList.add('unlocked');
            starElement.classList.remove('denied');
            void starElement.offsetWidth;
            starElement.classList.add('denied');
            return;
        }
        event.stopPropagation();
        if (cachedStars[key]) delete cachedStars[key];
        else cachedStars[key] = true;

        const storageKey = `${siteKey}_stars_${currentChatId}`;
        localStorage.setItem(storageKey, JSON.stringify(cachedStars));
        scanMessages(true);
    }

    // 确保容器存在
    function ensureContainer() {
        if (!document.getElementById(NAV_CONTAINER_ID)) {
            const container = document.createElement('div');
            container.id = NAV_CONTAINER_ID;
            document.documentElement.appendChild(container);
        }
    }

    // 获取最佳查询列表
    function getBestQueryList() {
        if (currentPlatform.customQueryList) return currentPlatform.customQueryList();
        let bestQueries = [];
        for (let s of currentPlatform.selectors) {
            const res = document.querySelectorAll(s);
            if (res.length > bestQueries.length) bestQueries = res;
        }
        return bestQueries;
    }

    // 瞬间跳转与高亮
    function handleJump(index) {
        const bestQueries = getBestQueryList();
        const target = bestQueries[index];
        if (target) {
            target.scrollIntoView({ behavior: 'instant', block: 'center' });
            
            const originalTransition = target.style.transition;
            const originalBg = target.style.backgroundColor;

            target.style.transition = 'background-color 0.5s ease';
            target.style.backgroundColor = currentPlatform.highlightColor;

            setTimeout(() => {
                target.style.backgroundColor = originalBg || 'transparent';
                setTimeout(() => {
                    target.style.transition = originalTransition;
                }, 500);
            }, 1500);
        }
    }

    // 扫描消息并渲染
    function scanMessages(force = false) {
        const container = document.getElementById(NAV_CONTAINER_ID);
        if (!container) return;

        const queries = getBestQueryList();
        if (queries.length === 0 && !force) return;
        
        const shouldRebuildNav = force || (queries.length !== container.children.length);

        if (shouldRebuildNav) {
             container.textContent = '';
        }

        const textFrequencyMap = {};

        Array.from(queries).forEach((el, index) => {
            // 注入页面内索引
            const indexLabel = `Q${index + 1}`;
            if (el.getAttribute('data-ai-index') !== indexLabel) {
                el.setAttribute('data-ai-index', indexLabel);
            }

            if (!shouldRebuildNav) return;

            let rawText = currentPlatform.getText(el) || `Question ${index + 1}`;
            rawText = rawText.replace(/\s+/g, ' ').trim();

            const currentCount = textFrequencyMap[rawText] || 0;
            textFrequencyMap[rawText] = currentCount + 1;
            const uniqueKey = getMessageKey(rawText, currentCount);

            const shortText = rawText.length > 25 ? rawText.substring(0, 25) + '...' : rawText;
            const isStarred = !!cachedStars[uniqueKey];

            const capsule = document.createElement('div');
            capsule.className = 'nav-capsule';
            if (isStarred) capsule.classList.add('starred');

            capsule.onmouseleave = () => {
                const s = capsule.querySelector('.capsule-star');
                if(s) s.classList.remove('unlocked');
            };

            capsule.onclick = () => handleJump(index);

            const indexSpan = document.createElement('span');
            indexSpan.className = 'capsule-index';
            indexSpan.textContent = indexLabel;

            const textSpan = document.createElement('span');
            textSpan.className = 'capsule-text';
            textSpan.textContent = shortText;

            const starIcon = document.createElement('span');
            starIcon.className = 'capsule-star';
            starIcon.textContent = isStarred ? '★' : '☆';
            starIcon.title = isStarred ? '取消收藏' : '点击两次以收藏';
            starIcon.onclick = (e) => toggleStar(uniqueKey, e, starIcon, isStarred);

            capsule.appendChild(indexSpan);
            capsule.appendChild(textSpan);
            capsule.appendChild(starIcon);
            container.appendChild(capsule);
        });
    }

})();