Greasy Fork

来自缓存

Greasy Fork is available in English.

AI Context Index

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

您需要先安装一款用户脚本管理器扩展,例如 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();

}) ();