AI 导航栏(Gemini & ChatGPT & deepseek)

三合一通用版。支持 Gemini、ChatGPT、DeepSeek。

目前為 2025-12-04 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         AI 导航栏(Gemini & ChatGPT & deepseek)
// @namespace    https://github.com/kuilei98/ai-conversation-sidebar
// @version      1.0.1.1204
// @description  三合一通用版。支持 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
// @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);
        });
    }

})();