AI Context Index

AI聊天目录,快速回溯,精准定位。支持ChatGPT、claude、grok、豆包、KIMI、千问、Gemini

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

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

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         AI Context Index
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  AI聊天目录,快速回溯,精准定位。支持ChatGPT、claude、grok、豆包、KIMI、千问、Gemini
// @author       Yekyos
// @license      MIT
// @match        *://*.doubao.com/*
// @match        https://kimi.moonshot.cn/*
// @match        https://*.kimi.com/*
// @match        https://gemini.google.com/*
// @match        https://chatgpt.com/*
// @match        https://chat.openai.com/*
// @match        https://tongyi.aliyun.com/*
// @match        https://*.qianwen.com/*
// @match        https://qianwen.aliyun.com/*
// @match        https://grok.com/*
// @match        https://x.com/*
// @match        https://chat.deepseek.com/*
// @match        https://claude.ai/*
// @grant        GM_addStyle
// @run-at       document-idle
// ==/UserScript==

(function () {
    'use strict';

    // Helper: CSS Injection with Fallback
    function addStyle(css) {
        if (typeof GM_addStyle !== 'undefined') {
            GM_addStyle(css);
        } else {
            const style = document.createElement('style');
            style.textContent = css;
            document.head.appendChild(style);
        }
    }

    // CSS Definitions (from src/styles.css)
    const styles = `
    /* 侧边栏容器 - 默认收起状态 */
    #doubao-sidebar-container {
      --ds-bg-color: rgba(255, 255, 255, 0.98);
      --ds-border-color: #eee;
      --ds-shadow-color: rgba(0, 0, 0, 0.12);
      --ds-text-color: #666;
      --ds-text-hover-color: #002124;
      --ds-item-hover-bg: rgba(0, 0, 0, 0.02);
      --ds-indicator-color: #ddd;
      --ds-indicator-hover-color: #002124;
      --ds-active-color: #006cff;
      --ds-scrollbar-thumb: #e0e0e0;
      --ds-scrollbar-thumb-hover: #ccc;

      position: fixed;
      top: 50%;
      transform: translateY(-50%);
      right: 20px;
      width: 320px;
      max-height: 500px;
      background-color: transparent;
      border: 1px solid transparent;
      border-radius: 20px;
      filter: drop-shadow(0 0 0 transparent);
      clip-path: inset(0 0 0 280px round 20px);
      z-index: 2147483647;
      display: flex;
      flex-direction: column;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
      transition: background-color 0.3s ease, border-color 0.3s ease, filter 0.3s ease, clip-path 0s linear 0.3s;
      overflow: hidden;
      padding: 12px 0;
      box-sizing: border-box;
    }

    html.dark #doubao-sidebar-container,
    body.dark #doubao-sidebar-container,
    [data-theme="dark"] #doubao-sidebar-container,
    [data-color-mode="dark"] #doubao-sidebar-container,
    #doubao-sidebar-container.ds-dark {
      --ds-bg-color: rgba(32, 33, 35, 0.98);
      --ds-border-color: #444;
      --ds-shadow-color: rgba(0, 0, 0, 0.5);
      --ds-text-color: #aaa;
      --ds-text-hover-color: #e0e0e0;
      --ds-item-hover-bg: rgba(255, 255, 255, 0.05);
      --ds-indicator-color: #555;
      --ds-indicator-hover-color: #ccc;
      --ds-active-color: #66b2ff;
      --ds-scrollbar-thumb: #555;
      --ds-scrollbar-thumb-hover: #777;
    }

    #doubao-sidebar-container:hover {
      background-color: var(--ds-bg-color);
      border-color: var(--ds-border-color);
      filter: drop-shadow(0 8px 24px var(--ds-shadow-color));
      padding: 12px 0;
      clip-path: inset(-50px -50px -50px -50px round 20px);
      transition: background-color 0.3s ease, border-color 0.3s ease, filter 0.3s ease, clip-path 0s linear 0s;
    }

    #doubao-sidebar-container * {
      box-sizing: border-box;
    }

    .ds-list {
      flex: 1;
      overflow-y: hidden;
      padding: 0;
      margin: 0;
      list-style: none;
      padding-right: 0;
    }

    #doubao-sidebar-container:hover .ds-list {
      overflow-y: overlay;
      overscroll-behavior: contain;
    }

    .ds-item {
      padding: 8px 12px;
      cursor: pointer;
      display: flex;
      align-items: center;
      transition: background-color 0.2s;
      position: relative;
      min-height: 24px;
    }

    .ds-item:hover {
      background-color: var(--ds-item-hover-bg);
    }

    .ds-text {
      font-size: 13px;
      color: var(--ds-text-color);
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      opacity: 0;
      transition: opacity 0.3s ease;
      flex: 1;
      text-align: left;
      padding-left: 8px;
      margin-right: 12px;
    }

    #doubao-sidebar-container:hover .ds-text {
      opacity: 1;
    }

    .ds-indicator-wrapper {
      display: flex;
      align-items: center;
      justify-content: flex-end;
      width: 16px;
      height: 16px;
      flex-shrink: 0;
      margin-left: auto;
    }

    .ds-indicator {
      display: block;
      width: 8px;
      height: 2px;
      background-color: var(--ds-indicator-color);
      border-radius: 2px;
      transition: all 0.3s ease;
    }

    .ds-item.active .ds-indicator {
      background-color: var(--ds-active-color);
      width: 14px;
      height: 3px;
    }

    .ds-item.active .ds-text {
      color: var(--ds-active-color);
      font-weight: 500;
    }

    .ds-item:not(.active):hover .ds-text {
      color: var(--ds-text-hover-color);
    }

    .ds-item:not(.active):hover .ds-indicator {
      background-color: var(--ds-indicator-hover-color);
    }

    .ds-item.active:hover .ds-indicator {
      background-color: var(--ds-active-color);
    }

    /* Scrollbar */
    .ds-list::-webkit-scrollbar {
      width: 4px;
    }
    .ds-list::-webkit-scrollbar-track {
      background: transparent;
    }
    .ds-list::-webkit-scrollbar-thumb {
      background: transparent;
      border-radius: 2px;
    }
    #doubao-sidebar-container:hover .ds-list::-webkit-scrollbar-thumb {
      background: var(--ds-scrollbar-thumb);
    }
    #doubao-sidebar-container:hover .ds-list::-webkit-scrollbar-thumb:hover {
      background: var(--ds-scrollbar-thumb-hover);
    }
    `;

    addStyle(styles);

    // Platform Configuration
    const PLATFORMS = {
        DOUBAO: 'doubao',
        KIMI: 'kimi',
        GEMINI: 'gemini',
        CHATGPT: 'chatgpt',
        QWEN: 'qwen',
        GROK: 'grok',
        CLAUDE: 'claude',
        DEEPSEEK: 'deepseek'
    };

    let currentPlatform = null;

    const CONFIG = {
        [PLATFORMS.DOUBAO]: {
            userMessageParent: 'div[data-testid="send_message"]',
            getText: (el) => el.innerText.trim()
        },
        [PLATFORMS.KIMI]: {
            containerSelector: '.chat-content-item.chat-content-item-user',
            textSelector: '.user-content',
            getText: (el) => {
                const textEl = el.querySelector('.user-content');
                return textEl ? textEl.innerText.trim() : '';
            }
        },
        [PLATFORMS.DEEPSEEK]: {
            containerSelector: 'div[data-testid="ds-message"]',
            getText: (el) => el.innerText.trim()
        },
        [PLATFORMS.GEMINI]: {
            containerSelector: 'user-query-content, .user-query-content, .user-query-container',
            textSelector: '.query-text-line',
            getText: (el) => {
                const textEl = el.querySelector('.query-text') || el.querySelector('.query-text-line') || el.querySelector('p');
                return textEl ? textEl.innerText.trim() : (el.innerText ? el.innerText.trim() : '');
            }
        },
        [PLATFORMS.CHATGPT]: {
            containerSelector: '[data-message-author-role="user"]',
            textSelector: '.whitespace-pre-wrap',
            getText: (el) => {
                const textEl = el.querySelector('.whitespace-pre-wrap');
                return textEl ? textEl.innerText.trim() : el.innerText.trim();
            }
        },
        [PLATFORMS.QWEN]: {
            containerSelector: 'div[class*="questionItem-"][data-msgid]',
            textSelector: 'div[class*="bubble-"]',
            getText: (el) => {
                const textEl = el.querySelector('div[class*="bubble-"]');
                return textEl ? textEl.innerText.trim() : el.innerText.trim();
            }
        },
        [PLATFORMS.GROK]: {
            containerSelector: '.r-1sw30gj',
            getText: (el) => el.innerText.trim()
        },
        [PLATFORMS.CLAUDE]: {
            containerSelector: 'div[data-testid="user-message"]',
            getText: (el) => el.innerText.trim()
        }
    };

    // State
    let debounceTimer = null;
    let observer = null;
    let sidebarList = null;

    // --- Core Functions ---

    function detectPlatform() {
        const hostname = window.location.hostname;
        if (hostname.includes('doubao.com')) {
            currentPlatform = PLATFORMS.DOUBAO;
        } else if (hostname.includes('kimi.moonshot.cn') || hostname.includes('kimi.com')) {
            currentPlatform = PLATFORMS.KIMI;
        } else if (hostname.includes('gemini.google.com')) {
            currentPlatform = PLATFORMS.GEMINI;
        } else if (hostname.includes('chatgpt.com') || hostname.includes('openai.com')) {
            currentPlatform = PLATFORMS.CHATGPT;
        } else if (hostname.includes('tongyi.aliyun.com') || hostname.includes('qianwen.com')) {
            currentPlatform = PLATFORMS.QWEN;
        } else if (hostname.includes('grok.com') || (hostname.includes('x.com') && location.pathname.includes('grok'))) {
            currentPlatform = PLATFORMS.GROK;
        } else if (hostname.includes('claude.ai')) {
            currentPlatform = PLATFORMS.CLAUDE;
        }
        else if (hostname.includes('chat.deepseek.com')) {
        currentPlatform = PLATFORMS.DEEPSEEK;
        }

    if (currentPlatform) {
        console.log('[AIChatIndex] Platform detected:', currentPlatform);
        init();
    } else {
        console.log('[AIChatIndex] No supported platform detected.');
    }
}

    function createSidebar() {
    // Create container
    const container = document.createElement('div');
    container.id = 'doubao-sidebar-container';

    // Create list
    const list = document.createElement('ul');
    list.className = 'ds-list';
    container.appendChild(list);
    sidebarList = list;

    // Append to body
    document.body.appendChild(container);
}

function updateSidebar(messages) {
    if (!sidebarList) return;

    // Optimization: Check if content changed significantly
    // For now, simpler to rebuild to ensure click handlers are fresh
    sidebarList.innerHTML = '';

    messages.forEach((msg, index) => {
        const li = document.createElement('li');
        li.className = 'ds-item';

        // Text
        const textSpan = document.createElement('span');
        textSpan.className = 'ds-text';
        textSpan.textContent = msg.text;
        textSpan.title = msg.text; // Tooltip for full text
        li.appendChild(textSpan);

        // Indicator Wrapper
        const indicatorWrapper = document.createElement('div');
        indicatorWrapper.className = 'ds-indicator-wrapper';

        // Indicator
        const indicator = document.createElement('span');
        indicator.className = 'ds-indicator';
        indicatorWrapper.appendChild(indicator);
        li.appendChild(indicatorWrapper);

        // Click Event
        li.addEventListener('click', (e) => {
            e.stopPropagation();
            msg.element.scrollIntoView({ behavior: 'smooth', block: 'center' });

            // Highlight active
            const allItems = sidebarList.querySelectorAll('.ds-item');
            allItems.forEach(item => item.classList.remove('active'));
            li.classList.add('active');
        });

        sidebarList.appendChild(li);
    });

    // Toggle visibility based on content
    const container = document.getElementById('doubao-sidebar-container');
    if (container) {
        container.style.display = messages.length > 0 ? 'flex' : 'none';
    }
}

function getMessages() {
    const config = CONFIG[currentPlatform];
    if (!config) return [];

    let elements = [];
    try {
        if (currentPlatform === PLATFORMS.DOUBAO) {
            if (config.userMessageParent) {
                elements = Array.from(document.querySelectorAll(config.userMessageParent));
            }
        } else if (config.containerSelector) {
            elements = Array.from(document.querySelectorAll(config.containerSelector));
        }
    } catch (e) {
        console.error('[AIChatIndex] Error querying elements:', e);
        return [];
    }

    const messages = elements.map(el => {
        try {
            const text = config.getText(el);
            return { element: el, text: text };
        } catch (e) {
            return null;
        }
    }).filter(item => item && item.text && item.text.length > 0);

    return messages;
}

function startObserving() {
    // Initial load
    const messages = getMessages();
    updateSidebar(messages);

    // Observer
    if (observer) observer.disconnect();
    observer = new MutationObserver(() => {
        if (debounceTimer) clearTimeout(debounceTimer);
        debounceTimer = setTimeout(() => {
            const msgs = getMessages();
            updateSidebar(msgs);
        }, 300);
    });

    observer.observe(document.body, { childList: true, subtree: true });
}

function init() {
    // Remove existing if any (for hot-reload dev)
    const existing = document.getElementById('doubao-sidebar-container');
    if (existing) existing.remove();

    createSidebar();
    startObserving();

    // SPA URL Change Detection
    let lastUrl = location.href;
    new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            setTimeout(() => {
                const msgs = getMessages();
                updateSidebar(msgs);
            }, 1000); // Wait for page load
        }
    }).observe(document, { subtree: true, childList: true });
}

// Start
detectPlatform();

}) ();