AI Conversation Navigator

Floating navigator for your prompts in ChatGPT and Gemini conversations

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         AI Conversation Navigator
// @namespace    https://greasyforks.org
// @version      1.4
// @description  Floating navigator for your prompts in ChatGPT and Gemini conversations
// @author       Bui Quoc Dung
// @match        https://chatgpt.com/*
// @match        https://gemini.google.com/*
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const COMMON_TITLE = 'Your Prompts';
    const COMMON_CONTAINER_STYLE = `
        right: 20px; width: 250px; max-height: 90vh; overflow-y: auto;
        z-index: 9999; border-radius: 2px; box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.3);
        transition: width 0.3s, padding 0.3s, opacity 0.3s, transform 0.3s;
        font-family: Calibri, sans-serif; font-size: 15px;
    `;

    const SITE_CONFIG = {
        chatgpt: {
            match: /chatgpt\.com/,
            msgSelector: 'div[data-message-author-role="user"]',
            conversationSelector: 'a.group.__menu-item.hoverable',
            mainElemSelector: 'main',
            moveLeftStyle: `
                body.navigator-expanded .mx-auto,
                body.navigator-expanded .md\\:max-w-3xl,
                body.navigator-expanded .xl\\:max-w-\\[48rem\\],
                body.navigator-expanded .max-w-2xl,
                body.navigator-expanded .lg\\:px-2 {
                    margin-left: 0 !important;
                    margin-right: 130px !important;
                }
                body.navigator-expanded main,
                body.navigator-expanded main > div,
                body.navigator-expanded main article > div,
                body.navigator-expanded div.flex.flex-col.items-center.text-sm {
                    margin-left: 0 !important;
                    margin-right: auto !important;
                    max-width: 100% !important;
                }
                body.navigator-expanded div.ProseMirror {
                    margin-left: 0 !important;
                    margin-right: auto !important;
                }
                body.navigator-expanded .xl\\:max-w-\\[48rem\\] {
                    width: 800px !important;
                    max-width: 100% !important;
                }
            `,
            containerStyle: `position: fixed; top: 35px; ${COMMON_CONTAINER_STYLE}`,
            bgClass: "text-token-text-primary bg-token-main-surface-primary rounded-lg shadow-lg"
        },
        gemini: {
            match: /gemini\.google\.com/,
            msgSelector: '.user-query-bubble-with-background',
            conversationSelector: 'div[role="button"][data-test-id="conversation"]',
            mainElemSelector: 'chat-window-content',
            moveLeftStyle: `
                body.navigator-expanded .chat-history-scroll-container {
                    margin-left: 0 !important;
                    margin-right: 300px !important;
                    max-width: calc(100% - 300px) !important;
                    transition: margin-right 0.3s ease;
                }
            `,
            containerStyle: `position: fixed; top: 55px; ${COMMON_CONTAINER_STYLE}`,
            bgClass: ""
        }
    };

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

    GM_addStyle(site.moveLeftStyle);

    let userMsgCounter = 0;
    let conversationObserver = null;
    let isCollapsed = false;

    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 = site.containerStyle;

            const header = document.createElement('div');
            header.style.display = 'flex';
            header.style.alignItems = 'center';
            header.style.justifyContent = 'space-between';
            header.style.padding = '5px';
            header.style.cursor = 'pointer';
            header.style.fontWeight = 'bold';
            header.textContent = COMMON_TITLE;

            const toggleBtn = document.createElement('button');
            toggleBtn.style.background = 'none';
            toggleBtn.style.border = 'none';
            toggleBtn.style.cursor = 'pointer';
            toggleBtn.style.fontSize = '16px';
            toggleBtn.textContent = '⯈';
            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 = '130px';
                toggleBtn.textContent = '⯆';
            }

            toggleBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                isCollapsed = !isCollapsed;
                if (!isCollapsed) {
                    content.style.display = 'block';
                    container.style.width = '250px';
                    toggleBtn.textContent = '⯈';
                } else {
                    content.style.display = 'none';
                    container.style.width = '130px';
                    toggleBtn.textContent = '⯆';
                }
                updateBodyClassForLayout();
            });

            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}`;
        listItem.style.cursor = 'pointer';
        listItem.style.padding = '5px 5px';

        listItem.addEventListener('click', () => {
            const allItems = listItem.parentElement.querySelectorAll('li');
            allItems.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.padding = '0';
            list.style.margin = '0';
            list.style.listStyle = 'none';
            content.appendChild(list);
        }

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

        if (userMessages.length < existingListItems.length) {
            list.innerHTML = '';
        }

        if (userMessages.length > existingListItems.length) {
            for (let i = existingListItems.length; i < userMessages.length; i++) {
                const msgElem = userMessages[i];
                assignIdToMessage(msgElem);
                const listItem = createListItem(msgElem);
                list.appendChild(listItem);
            }
        }
    }

    function observeConversation() {
        if (conversationObserver) conversationObserver.disconnect();
        const mainElem = document.querySelector(site.mainElemSelector);
        if (!mainElem) return;
        conversationObserver = new MutationObserver(() => updateMessageList());
        conversationObserver.observe(mainElem, { childList: true, subtree: true });
    }

    function waitForChatToLoad() {
        const interval = setInterval(() => {
            const mainElem = document.querySelector(site.mainElemSelector);
            if (mainElem && document.querySelector(site.msgSelector)) {
                clearInterval(interval);
                userMsgCounter = 0;
                const content = document.getElementById('message-nav-content');
                if (content) {
                    const list = content.querySelector('ul');
                    if (list) list.innerHTML = '';
                }
                updateMessageList();
                observeConversation();
            }
        }, 500);
    }

    function attachConversationClickListener() {
        document.body.addEventListener('click', (e) => {
            const a = e.target.closest(site.conversationSelector);
            if (!a) return;
            userMsgCounter = 0;
            const content = document.getElementById('message-nav-content');
            if (content) {
                const list = content.querySelector('ul');
                if (list) list.innerHTML = '';
            }

            setTimeout(updateMessageList, 300);
        });
    }

    attachConversationClickListener();
    waitForChatToLoad();

    let lastUrl = location.href;
    new MutationObserver(() => {
        if (location.href !== lastUrl) {
            lastUrl = location.href;
            waitForChatToLoad();
        }
    }).observe(document.body, { childList: true, subtree: true });

})();
长期地址
遇到问题?请前往 GitHub 提 Issues。