AI Context Index

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

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

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

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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

}) ();