Greasy Fork

Greasy Fork is available in English.

AI Conversation Navigator

Floating navigator for your prompts in ChatGPT, Gemini, Notebooklm, Kimi, Grok, GLM conversations

当前为 2025-11-28 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         AI Conversation Navigator
// @namespace    http://greasyfork.icu
// @version      2.7
// @description  Floating navigator for your prompts in ChatGPT, Gemini, Notebooklm, Kimi, Grok, GLM conversations
// @author       Bui Quoc Dung
// @match        https://chatgpt.com/*
// @match        https://gemini.google.com/*
// @match        https://notebooklm.google.com/*
// @match        https://grok.com/*
// @match        https://www.kimi.com/*
// @match        https://chat.deepseek.com/*
// @match        https://chat.z.ai/*
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';
    const NAV_WIDTH = 250;
    const NAV_COLLAPSED_WIDTH = 80;

    const BASE_CONTAINER_CSS = `
        right: 0px; width: ${NAV_WIDTH}px; max-height: 90vh; overflow-y: auto;
        z-index: 9999;
        transition: width 0.3s, padding 0.3s, opacity 0.3s, transform 0.3s;
        font-family: Calibri, sans-serif; font-size: 15px; color: CanvasText; position: fixed;
    `;

    const getShiftStyle = (width, selector = '') => `
        body.navigator-expanded ${selector} {
            margin-left: 0 !important;
            margin-right: ${width}px !important;
            max-width: calc(100% - ${width}px) !important;
            transition: margin-right 0.3s ease;
        }
    `;

    const SITE_CONFIG = {
        chatgpt: {
            match: /chatgpt\.com/,
            msgSelector: 'div[data-message-author-role="user"]',
            top: '55px',
            bgClass: "text-token-text-primary bg-token-main-surface-primary rounded-lg shadow-lg"
        },
        gemini: {
            match: /gemini\.google\.com/,
            msgSelector: '.query-text',
            top: '55px'
        },
        notebooklm: {
            match: /notebooklm\.google\.com/,
            msgSelector: 'chat-message .from-user-container',
            top: '55px',
        },
        grok: {
            match: /grok\.com/,
            msgSelector: '.relative.group.flex.flex-col.justify-center.items-end',
            top: '55px'
        },
        deepseek: {
            match: /chat\.deepseek\.com/,
            msgSelector: '.ds-message:nth-child(odd)',
            top: '55px',
            shiftTarget: '#root > div > div > div:nth-child(2) > div:nth-child(3) > div > div:nth-child(2) > div'
        },
        kimi: {
            match: /www\.kimi\.com/,
            msgSelector: '.user-content',
            top: '55px'
        },
        glm: {
            match: /chat\.z\.ai/,
            msgSelector: '.chat-user',
            top: '55px'
        }
    };

    const site = Object.values(SITE_CONFIG).find(s => s.match.test(location.hostname));
    if (!site) return;
    const currentWidth = site.width || NAV_WIDTH;

    GM_addStyle(getShiftStyle(currentWidth, site.shiftTarget || ''));

    let userMsgCounter = 0;
    let conversationObserver = null;
    let isCollapsed = false;
    window.navigatorUpdateTimeout = null;

    function updateBodyClassForLayout() {
        const container = document.getElementById('message-nav');
        const content = document.getElementById('message-nav-content');
        if (container && content && content.style.display !== 'none') {
            document.body.classList.add('navigator-expanded');
        } else {
            document.body.classList.remove('navigator-expanded');
        }
    }

    function createContainer() {
        let container = document.getElementById('message-nav');
        if (!container) {
            container = document.createElement('div');
            container.id = 'message-nav';
            container.className = site.bgClass || '';
            container.style.cssText = `top: ${site.top}; ${BASE_CONTAINER_CSS}`;

            const header = document.createElement('div');
            Object.assign(header.style, {
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                cursor: 'pointer',
                fontWeight: 'bold'
            });
            const toggleBtn = document.createElement('button');
            Object.assign(toggleBtn.style, {
                background: 'none',
                border: 'none',
                cursor: 'pointer',
                fontSize: '18px',
                color: 'inherit'
            });
            toggleBtn.textContent = 'Close';
            header.appendChild(toggleBtn);

            const content = document.createElement('div');
            content.id = 'message-nav-content';
            content.style.padding = '5px';

            container.appendChild(header);
            container.appendChild(content);
            document.body.appendChild(container);

            if (isCollapsed) {
                content.style.display = 'none';
                container.style.width = `${NAV_COLLAPSED_WIDTH}px`;
                toggleBtn.textContent = 'Open';
            }

            const toggleHandler = (e) => {
                e.stopPropagation();
                isCollapsed = !isCollapsed;

                if (!isCollapsed) {
                    content.style.display = 'block';
                    container.style.width = `${currentWidth}px`;
                    toggleBtn.textContent = 'Close';
                } else {
                    content.style.display = 'none';
                    container.style.width = `${NAV_COLLAPSED_WIDTH}px`;
                    toggleBtn.textContent = 'Open';
                }

                updateBodyClassForLayout();
            };

            toggleBtn.addEventListener('click', toggleHandler);
            updateBodyClassForLayout();
        }
        return container;
    }

    function assignIdToMessage(msgElem) {
        if (!msgElem.id) {
            userMsgCounter++;
            msgElem.id = 'user-msg-' + userMsgCounter;
            msgElem.dataset.index = userMsgCounter;
        }
    }

    function createListItem(msgElem) {
        const index = msgElem.dataset.index || '?';
        const text = msgElem.innerText.trim();
        const preview = text.length > 80 ? text.slice(0, 80) + '...' : text;

        const listItem = document.createElement('li');
        listItem.textContent = `${index}. ${preview}`;
        Object.assign(listItem.style, { cursor: 'pointer', padding: '5px 0px 5px 5px' });

        listItem.addEventListener('click', () => {
            listItem.parentElement.querySelectorAll('li').forEach(li => li.style.fontWeight = 'normal');
            listItem.style.fontWeight = 'bold';
            msgElem.scrollIntoView({ behavior: 'smooth', block: 'start' });
        });

        return listItem;
    }

    function updateMessageList() {
        const container = createContainer();
        const content = document.getElementById('message-nav-content');
        if (!content) return;

        let list = content.querySelector('ul');
        if (!list) {
            list = document.createElement('ul');
            list.style.cssText = 'padding: 0; margin: 0; list-style: none;';
            content.appendChild(list);
        }

        const userMessages = document.querySelectorAll(site.msgSelector);
        const existingListItems = list.querySelectorAll('li');
        let shouldReset = false;

        if (userMessages.length < existingListItems.length) {
            shouldReset = true;
        }

        if (!shouldReset && userMessages.length > 0 && existingListItems.length > 0) {
            const firstMsgDOM = userMessages[0];
            const firstListItem = existingListItems[0];
            if (!firstMsgDOM.id || (firstListItem.dataset.msgId && firstMsgDOM.id !== firstListItem.dataset.msgId)) {
                shouldReset = true;
            }
        }

        if (shouldReset) {
            list.innerHTML = '';
            userMsgCounter = 0;
        }

        const currentItems = list.querySelectorAll('li');
        if (userMessages.length > currentItems.length) {
            for (let i = currentItems.length; i < userMessages.length; i++) {
                const msgElem = userMessages[i];
                assignIdToMessage(msgElem);
                const listItem = createListItem(msgElem);
                listItem.dataset.msgId = msgElem.id;
                list.appendChild(listItem);
            }
        }
    }

    function observeConversation() {
        if (conversationObserver) conversationObserver.disconnect();
        conversationObserver = new MutationObserver(() => {
            clearTimeout(window.navigatorUpdateTimeout);
            window.navigatorUpdateTimeout = setTimeout(() => updateMessageList(), 500);
        });
        conversationObserver.observe(document.body, { childList: true, subtree: true, attributes: false });
    }

    setTimeout(() => updateMessageList(), 500);
    observeConversation();

})();