AI Context Index

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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();

}) ();