AI Conversation Navigator

Floating navigator for your prompts in conversations with bookmark feature. Applied for ChatGPT, Gemini, Aistudio, NotebookLM, Google search, Grok, Claude, Mistral, Meta, Deepseek, Kimi, Qwen, Z.ai, Chatglm, Ernie, Xiaomimimo, Perplexity, Poe, Deepai, Huggingface, Manus, Longcat, Chatboxai, Lmarena, Quillbot, Canva, Genspark, Character, Spacefrontiers, Scienceos, Evidencehunt, Playground (allen), Paperfigureqa (allen), Liner, Scira, Scispace, Exa.ai, Consensus, Openevidence, Math-gpt.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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 Conversation Navigator
// @namespace    https://greasyfork.org
// @version      12.9
// @description  Floating navigator for your prompts in conversations with bookmark feature. Applied for ChatGPT, Gemini, Aistudio, NotebookLM, Google search, Grok, Claude, Mistral, Meta, Deepseek, Kimi, Qwen, Z.ai, Chatglm, Ernie, Xiaomimimo, Perplexity, Poe, Deepai, Huggingface, Manus, Longcat, Chatboxai, Lmarena, Quillbot, Canva, Genspark, Character, Spacefrontiers, Scienceos, Evidencehunt, Playground (allen), Paperfigureqa (allen), Liner, Scira, Scispace, Exa.ai, Consensus, Openevidence, Math-gpt.
// @author       Bui Quoc Dung
// @match        https://chatgpt.com/*
// @match        https://gemini.google.com/*
// @match        https://aistudio.google.com/*
// @match        https://notebooklm.google.com/*
// @match        https://www.google.com/*
// @match        https://grok.com/*
// @match        https://claude.ai/*
// @match        https://www.kimi.com/*
// @match        https://chat.mistral.ai/*
// @match        https://www.perplexity.ai/*
// @match        https://www.meta.ai/*
// @match        https://poe.com/*
// @match        https://deepai.org/*
// @match        https://huggingface.co/chat/*
// @match        https://chat.deepseek.com/*
// @match        https://chat.qwen.ai/*
// @match        https://manus.im/*
// @match        https://chat.z.ai/*
// @match        https://longcat.chat/*
// @match        https://chatglm.cn/*
// @match        https://ernie.baidu.com/*
// @match        https://aistudio.xiaomimimo.com/*
// @match        https://web.chatboxai.app/*
// @match        https://lmarena.ai/*
// @match        https://quillbot.com/*
// @match        https://www.canva.com/*
// @match        https://www.genspark.ai/*
// @match        https://character.ai/*
// @match        https://spacefrontiers.org/*
// @match        https://app.scienceos.ai/*
// @match        https://evidencehunt.com/*
// @match        https://playground.allenai.org/*
// @match        https://paperfigureqa.allen.ai/*
// @match        https://app.liner.com/*
// @match        https://scira.ai/*
// @match        https://exa.ai/*
// @match        https://consensus.app/*
// @match        https://www.openevidence.com/*
// @match        https://math-gpt.org/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @license      MIT
// ==/UserScript==


(function () {
    'use strict';

    const NAV_WIDTH = 250;
    const DEBOUNCE_TIME = 500;
    const AISTUDIO_DEBOUNCE_TIME = 200;
    const BOOKMARK_TTL_MS = 5 * 24 * 60 * 60 * 1000;
    const BOOKMARK_PREFIX = 'acn_';

    let activeMessageIndex = -1;
    let lastUrl = window.location.href;
    let lastPromptsContent = "";
    let cachedPrompts = [];
    let urlCheckInterval = null;
    let injectedStyleId = 'nav-shift-styles';
    let bookmarkedMessages = new Set();
    let conversationObserver = null;
    let isClosedByUser = false;
    window.navigatorUpdateTimeout = null;

    function injectStyles() {
        GM_addStyle(`
            #message-nav {
                position: fixed; top: 0; right: 0; bottom: 0; width: ${NAV_WIDTH}px; overflow-y: auto; z-index: 9999;
                font-family: Calibri, sans-serif; font-size: 17px; color: CanvasText; text-align: left;
                border-left: 1px solid color-mix(in srgb, CanvasText 15%, transparent);
            }
            #nav-header {
                display: flex; align-items: center; justify-content: center;
                position: sticky; top: 0; z-index: 10; padding: 12px 10px 12px 10px; background-color: Canvas;
                border-bottom: 1px solid color-mix(in srgb, CanvasText 15%, transparent);
            }
            #nav-buttons-group {
                display: flex; gap: 0; align-items: center;
            }
            .nav-control-btn {
                background: none; border: none; cursor: pointer; font-size: 16px; color: inherit; padding: 2px 5px;
            }
            #nav-btn-close {
                position: absolute; top: 0; right: 0; background: none; border: none; cursor: pointer; font-size: 15px; color: inherit;
                display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 4px;
            }
            #nav-message-counter {
                padding: 0; min-width: 40px; text-align: center; font-size: 15px; font-weight: normal; user-select: none;
                color: color-mix(in srgb, CanvasText 80%, transparent);
            }
            #message-nav-content {
                padding: 5px;
            }
            #nav-list {
                padding: 0; margin: 0; list-style: none;
            }
            .nav-list-item {
                cursor: pointer; padding: 5px 0 5px 5px; font-weight: normal; transition: font-weight 0.1s ease;
            }
            .nav-list-item.active {
                font-weight: bold !important; background-color: color-mix(in srgb, CanvasText 10%, transparent);
            }
            @keyframes nav-blink-animation {
                0% { opacity: 1; } 50% { opacity: 0.1; } 100% { opacity: 1; }
            }
            .nav-blink-active {
                animation: nav-blink-animation 0.5s ease-in-out 3;
            }
            .nav-item-number {
                display: inline; cursor: pointer; user-select: none;
            }
            .nav-item-number.bookmarked {
                background-color: #fff59d !important; color: black !important; padding: 2px 4px; border-radius: 3px;
            }
            .nav-version-controls {
                display: inline-flex; align-items: center; margin-left: 5px; gap: 2px; border-radius: 10px; padding: 2px;
                border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
            }
            .nav-version-btn {
                background: none; border: none; cursor: pointer; font-size: 12px; padding: 1px 4px;
                border-radius: 3px; color: inherit; line-height: 1;
            }
            .nav-version-btn:hover {
                background-color: color-mix(in srgb, CanvasText 10%, transparent);
            }
            .nav-version-btn:disabled {
                opacity: 0.3; cursor: not-allowed;
            }
            .nav-version-text {
                font-size: 12px; color: color-mix(in srgb, CanvasText 70%, transparent); margin: 0 2px;
            }
            .nav-bookmarks-container {
                display: flex; align-items: center; gap: 5px; flex-wrap: wrap; padding: 4px 10px; background-color: Canvas;
                border-bottom: 1px solid color-mix(in srgb, CanvasText 10%, transparent);
                min-height: 34px; box-sizing: border-box; width: 100%; flex-shrink: 0; position: sticky; z-index: 9;
            }
            .nav-bookmark-item {
                background-color: #fff59d; color: black; padding: 3px 6px; border-radius: 3px;
                cursor: pointer; font-size: 15px; user-select: none; transition: background-color 0.2s;
            }
            .nav-bookmark-item:hover {
                background-color: #fff176;
            }
            .nav-bookmark-item.active {
                font-weight: bold; background-color: #ffeb3b;
            }
        `);

        const allUserSelectors = Object.values(SITE_CONFIGS)
            .map(config => config.userMessage?.container)
            .filter(selector => typeof selector === 'string' && selector.length > 0)
            .join(', ');

        if (allUserSelectors) {
            GM_addStyle(`${allUserSelectors} { scroll-margin-top: 10px !important; }`);
        }
    }

    function getAIStudioData() {
        const prompts = [];
        const scrollButtons = document.querySelectorAll('ms-prompt-scrollbar button[id^="scrollbar-item-"]');
        scrollButtons.forEach(btn => {
            const isUser = btn.querySelector('.prompt-scrollbar-dot');
            if (isUser) {
                const text = btn.getAttribute('aria-label');
                if (text) prompts.push({ element: btn, text: text.trim() });
            }
        });
        return prompts;
    }

    const SITE_CONFIGS = {
        chatgpt: {
            domain: 'chatgpt.com',
            userMessage: {
                container: 'div[data-message-author-role="user"]',
                file: '.p-2 .truncate.font-semibold',
                text: '.user-message-bubble-color',
                image: 'img'
            },
            shiftTarget: '.flex.h-svh.w-screen.flex-col',
            versionControl: {
                container: '.z-0.flex.justify-end',
                versionText: '.tabular-nums',
                prevButton: 'button[aria-label*="Previous response"]',
                nextButton: 'button[aria-label*="Next response"]'
            }
        },
        gemini: {
            domain: 'gemini.google.com',
            userMessage: {
                container: '.user-query-container .user-query-container .user-query-container',
                file: '.new-file-preview-container button[aria-label]',
                text: '.query-text p',
                image: '.preview-image'
            },
            shiftTarget: 'chat-app, .boqOnegoogleliteOgbOneGoogleBar, top-bar-actions',
        },
        aistudio: {
            domain: 'aistudio.google.com',
            customFinder: getAIStudioData,
            useClick: true,
            shiftTarget: '.layout-wrapper',
            fastUpdate: true,
            debounceTime: AISTUDIO_DEBOUNCE_TIME,
        },
        notebooklm: {
            domain: 'notebooklm.google.com',
            userMessage: { container: 'chat-message .from-user-container' },
            shiftTarget: 'notebook, .boqOnegoogleliteOgbOneGoogleBar',
        },
        googleSearch: {
            domain: 'www.google.com',
            userMessage: { container: '[aria-hidden="false"] [role="heading"][aria-level="2"][jsuid]' },
            shiftTarget: '[jsname="oEQ3x"], header[jsname="kNXmHc"], .eT9Cje',
        },
        grok: {
            domain: 'grok.com',
            userMessage: {
                container: '.relative.group.flex.flex-col.justify-center.items-end',
                file: '.flex.flex-row.flex-wrap.justify-end.gap-2.mt-2',
                text: '.message-bubble',
                image: 'img'
            },
            shiftTarget: 'main',
            versionControl: {
                container: '.relative.group.flex.flex-col.justify-center.items-end .action-buttons',
                versionText: '[class="px-0.5"]',
                prevButton: 'button[aria-label="Previous message"]',
                nextButton: 'button[aria-label="Next message"]'
            }
        },
        claude: {
            domain: 'claude.ai',
            userMessage: {
                container: '.mb-1.mt-6.group',
                file: '[data-testid="file-thumbnail"]',
                fileType: '.uppercase.truncate',
                fileName: 'p',
                image: '[data-testid*="."]',
                text: '[data-testid="user-message"]'
            },
            shiftTarget: '#main-content',
            versionControl: {
                container: '.mb-1.mt-6.group',
                versionText: '.self-center.select-none',
                prevButton: 'button[aria-label="Previous"]',
                nextButton: 'button[aria-label="Next"]'
            }
        },
        mistral: {
            domain: 'chat.mistral.ai',
            userMessage: {
                container: 'div[data-message-author-role="user"] div[dir="auto"]',
                file: '.max-w-2xs',
                image: 'img',
                text: '.select-none'
            },
            shiftTarget: 'main.bg-sidebar-subtle',
            versionControl: {
                container: 'div[data-message-author-role="user"]',
                versionText: '[class*="text-muted"][class*="dark:text-muted"]',
                prevButton: 'button[aria-label*="Previous version"]',
                nextButton: 'button[aria-label*="Next version"]'
            }
        },
        meta: {
            domain: 'meta.ai',
            userMessage: {
                container: '.xuk3077.x78zum5.xdt5ytf.x17zd0t2.x1r0jzty',
                file: 'a[download]',
                fileName: '.x1lliihq.x193iq5w.x6ikm8r.x10wlt62.xlyipyv.xuxw1ft',
                fileType: '.x1lgk290',
                text: '.xh8yej3 span',
                image: 'img'
            },
            shiftTarget: '.xph554m.x73z65k',
        },
        deepseek: {
            domain: 'chat.deepseek.com',
            userMessage: {
                container: '.d29f3d7d',
                file: '.f3a54b52',
                image: 'img',
                text: '._9663006 .fbb737a4'
            },
            shiftTarget: '._8f60047, ._189b4a0, ._2be88ba',
            versionControl: {
                container: '._9663006',
                versionText: '.dd7e4fda',
                prevButton: '.e7367035:first-of-type',
                nextButton: '.e7367035:last-of-type'
            }
        },
        kimi: {
            domain: 'www.kimi.com',
            userMessage: {
                container: '.segment.segment-user',
                file: '.attachment-list .file-card-info-name',
                image: '.image-wrapper.image-detail img',
                text: '.user-content'
            },
            shiftTarget: '.has-sidebar',
            versionControl: {
                container: '.segment-user-actions',
                versionText: '.assistant-page-info',
                prevButton: '.icon-button.assistant-page-item:first-of-type',
                nextButton: '.icon-button.assistant-page-item:last-of-type'
            }
        },
        glm: {
            domain: 'chat.z.ai',
            userMessage: {
                container: '.chat-user',
                file: '.mb-1.truncate',
                image: 'img.object-cover',
                text: '.flex.justify-end.pb-1'
            },
            shiftTarget: '#chat-container',
            versionControl: {
                container: '.chat-user .flex.justify-end.text-gray-600',
                versionText: '.self-center.text-sm.font-semibold.tracking-widest',
                prevButton: '.self-center.p-1.rounded-md.transition:first-of-type',
                nextButton: '.self-center.p-1.rounded-md.transition:last-of-type'
            }
        },
        qwen: {
            domain: 'chat.qwen.ai',
            userMessage: {
                container: '.chat-user-message-container',
                file: '.index-module__file-message___SeOoR',
                fileType: '.fileitem-file-name-ext',
                fileName: '.fileitem-file-name-text',
                image: 'img',
                text: '.chat-user-message'
            },
            shiftTarget: '.desktop-layout-content',
            versionControl: {
                container: '.user-message-footer.ant-flex',
                versionText: '.qwen-chat-ui-packages-siblings-text',
                prevButton: '.anticon.qwen-chat-ui-packages-siblings-active-icon:first-of-type',
                nextButton: '.anticon.qwen-chat-ui-packages-siblings-active-icon:last-of-type'
            }
        },
        chatglm: {
            domain: 'chatglm.cn',
            userMessage: { container: '.question-txt.dots' },
            shiftTarget: '.detail-container',
        },
        ernie: {
            domain: 'ernie.baidu.com',
            userMessage: {
                container: '.roleUser__TCPTqNDW',
                file: '.chat-file-item-card',
                fileType: '.metaDesc__zRkzT0lZ span:first-of-type',
                fileName: '.title__gkcd9NRs',
                image: '.singleImage__BG1t1bGa img',
                text: '#question_text_id'
            },
            shiftTarget: '#root',
            versionControl: {
                container: '.editControls__OdmgmAiJ',
                versionText: '.pageCount__SvI_mTsu',
                prevButton: '.turnIcon__oN1i9Cks:first-of-type',
                nextButton: '.turnIcon__oN1i9Cks:last-of-type'
            },
            reverse: true,
        },
        xiaomimimo: {
            domain: 'aistudio.xiaomimimo.com',
            userMessage: { container: '.relative.inline-block.whitespace-pre-wrap' },
            shiftTarget: '.overflow-hidden.bg-gray-50',
        },
        perplexity: {
            domain: 'perplexity.ai',
            userMessage: { container: 'div.group\\/title' },
            shiftTarget: '#root',
        },
        poe: {
            domain: 'poe.com',
            userMessage: { container: '[class*="ChatMessagesView_tupleGroupContainer"] > div > div:first-child' },
            shiftTarget: '[class*="CanvasSidebarLayout_chat-column"]'
        },
        deepai: {
            domain: 'deepai.org',
            userMessage: { container: '.chatbox' },
            shiftTarget: '.chat-layout-container, .new-chat-button-container, .persistent-compose-area, .nav-items'
        },
        huggingface: {
            domain: 'huggingface.co',
            userMessage: { container: '.disabled.w-full.appearance-none' },
            shiftTarget: '.relative.min-h-0.min-w-0'
        },
        manus: {
            domain: 'manus.im',
            userMessage: { container: '.flex.relative.flex-col.gap-2.items-end' },
            shiftTarget: '.simplebar-content'
        },
        longcat: {
            domain: 'longcat.chat',
            userMessage: { container: '.user-message' },
            shiftTarget: '.content',
        },
        chatboxai: {
            domain: 'web.chatboxai.app',
            userMessage: { container: '.user-msg' },
            shiftTarget: '.h-full.w-full.MuiBox-root'
        },
        lmarena: {
            domain: 'lmarena.ai',
            userMessage: { container: '.justify-end.gap-2' },
            shiftTarget: '#chat-area',
            reverse: true
        },
        quillbot: {
            domain: 'quillbot.com',
            userMessage: { container: 'div.MuiGrid-root.MuiGrid-container > div.MuiGrid-root > p.MuiTypography-root.MuiTypography-bodyMedium.MuiTypography-paragraph' },
            shiftTarget: '#root-client'
        },
        canva: {
            domain: 'www.canva.com',
            userMessage: { container: '#_r_1_ .uV9Uzw .Ka9auQ p' },
            shiftTarget: '#root'
        },
        genspark: {
            domain: 'www.genspark.ai',
            userMessage: { container: '.conversation-item-desc.user' },
            shiftTarget: '.n-config-provider'
        },
        character: {
            domain: 'character.ai',
            userMessage: { container: '.w-full .bg-surface-elevation-3.opacity-90' },
            shiftTarget: '#__next, #chat-header-background',
            reverse: true
        },
        spacefrontiers: {
            domain: 'spacefrontiers.org',
            userMessage: { container: '.inline.whitespace-pre-line' },
            shiftTarget: '#app'
        },
        scienceos: {
            domain: 'app.scienceos.ai',
            userMessage: { container: 'div[data-prompt]' },
            shiftTarget: 'div[data-strategy]',
            versionControl: {
                container: 'div:has(> button[aria-label*="thread"])',
                versionText: 'div[style*="tabular-nums"]',
                prevButton: 'button[aria-label="Previous thread"]',
                nextButton: 'button[aria-label="Next thread"]'
            },
        },
        evidencehunt: {
            domain: 'evidencehunt.com',
            userMessage: { container: '.chat__message:has(.message__user-image) .message__content p' },
            shiftTarget: '.v-main, .v-app-bar, .chat-tab-bar'
        },
        playground: {
            domain: 'playground.allenai.org',
            userMessage: { container: 'div[class*="chat-message"]:nth-of-type(even)' },
            shiftTarget: '.MuiPaper-outlined'
        },
        paperfigure: {
            domain: 'paperfigureqa.allen.ai',
            userMessage: { container: '#chat-scroll-container > div > div:nth-of-type(odd) .MuiPaper-root' },
            shiftTarget: '#root'
        },
        liner: {
            domain: 'app.liner.com',
            userMessage: { container: '#userQuestion' },
            shiftTarget: '.flex.min-h-0.w-full.flex-1'
        },
        scira: {
            domain: 'scira.ai',
            userMessage: { container: '.max-w-full .relative' },
            shiftTarget: '.sm\\:max-w-2xl'
        },
        exa: {
            domain: 'exa.ai',
            userMessage: { container: 'div[data-test-id="UserMessage"]' },
            shiftTarget: 'div[data-test-id="ChatPresentation"]'
        },
        consensus: {
            domain: 'consensus.app',
            userMessage: { container: '.flex.flex-col.pt-6.w-full.max-w-page h2' },
            shiftTarget: '#__next'
        },
        openevidence: {
            domain: 'openevidence.com',
            userMessage: { container: '.brandable--query-bar--container form' },
            shiftTarget: '#__next, .brandable--query-bar--container.hide-on-print.follow-up'
        },
        mathgpt: {
            domain: 'math-gpt.org',
            userMessage: { container: '.w-full.flex.items-end.flex-col.pb-8.relative' },
            shiftTarget: '.overflow-x-hidden, .px-2.flex.flex-col.gap-1'
        },
    };


    function getCurrentConfig() {
        const hostname = window.location.hostname;
        for (const key in SITE_CONFIGS) {
            if (hostname.includes(SITE_CONFIGS[key].domain)) return SITE_CONFIGS[key];
        }
        return null;
    }

    const CURRENT_SITE = getCurrentConfig();
    if (!CURRENT_SITE) return;

    function hasUserMessages() {
        if (CURRENT_SITE.customFinder) {
            const prompts = CURRENT_SITE.customFinder();
            return prompts && prompts.length > 0;
        }
        if (!CURRENT_SITE.userMessage || !CURRENT_SITE.userMessage.container) return false;
        return document.querySelectorAll(CURRENT_SITE.userMessage.container).length > 0;
    }

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

    function updateShiftStyles(shouldInject) {
        let existingStyle = document.getElementById(injectedStyleId);
        if (shouldInject && !existingStyle) {
            const currentWidth = CURRENT_SITE.width || NAV_WIDTH;
            let cssContent = '';
            if (CURRENT_SITE.shiftTarget) cssContent += getShiftStyle(currentWidth, CURRENT_SITE.shiftTarget);
            if (cssContent) {
                const styleElement = document.createElement('style');
                styleElement.id = injectedStyleId;
                styleElement.textContent = cssContent;
                document.head.appendChild(styleElement);
            }
        } else if (!shouldInject && existingStyle) {
            existingStyle.remove();
        }
    }

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

    function getStorageKey() {
        return BOOKMARK_PREFIX + window.location.hostname + window.location.pathname;
    }

    function saveBookmarks() {
        const key = getStorageKey();
        const data = Array.from(bookmarkedMessages);
        if (data.length === 0) {
            GM_deleteValue(key);
            return;
        }
        const value = Date.now() + '|' + data.sort((a, b) => a - b).join(',');
        GM_setValue(key, value);
    }

    function loadBookmarks() {
        const key = getStorageKey();
        const raw = GM_getValue(key, '');
        if (!raw) {
            bookmarkedMessages = new Set();
            return;
        }
        const sep = raw.indexOf('|');
        if (sep === -1) {
            GM_deleteValue(key);
            bookmarkedMessages = new Set();
            return;
        }
        const savedAt = parseInt(raw.slice(0, sep));
        const age = Date.now() - savedAt;
        if (age > BOOKMARK_TTL_MS) {
            GM_deleteValue(key);
            bookmarkedMessages = new Set();
            return;
        }
        const indices = raw.slice(sep + 1);
        bookmarkedMessages = new Set(
            indices.split(',').map(Number).filter(n => !isNaN(n) && n > 0)
        );
    }

    function cleanupExpiredBookmarks() {
        try {
            GM_listValues().forEach(key => {
                if (!key.startsWith(BOOKMARK_PREFIX)) return;
                const raw = GM_getValue(key, '');
                if (!raw) { GM_deleteValue(key); return; }
                const sep = raw.indexOf('|');
                if (sep === -1) { GM_deleteValue(key); return; }
                const age = Date.now() - parseInt(raw.slice(0, sep));
                if (age > BOOKMARK_TTL_MS) GM_deleteValue(key);
            });
        } catch (e) {}
    }

    function toggleBookmark(index) {
        if (bookmarkedMessages.has(index)) {
            bookmarkedMessages.delete(index);
        } else {
            bookmarkedMessages.add(index);
        }
        saveBookmarks();
        updateBookmarkVisuals();
        updateBookmarksHeader();
    }

    function updateBookmarkVisuals() {
        const content = document.getElementById('message-nav-content');
        if (!content) return;
        const list = content.querySelector('#nav-list');
        if (!list) return;
        list.querySelectorAll('.nav-list-item').forEach((item, idx) => {
            const numberContainer = item.querySelector('.nav-item-number');
            if (numberContainer) {
                const index = idx + 1;
                if (bookmarkedMessages.has(index)) {
                    numberContainer.textContent = `${index}`;
                    numberContainer.classList.add('bookmarked');
                } else {
                    numberContainer.textContent = `${index}.`;
                    numberContainer.classList.remove('bookmarked');
                }
            }
        });
    }

    function updateBookmarksHeader() {
        let bookmarksContainer = document.getElementById('nav-bookmarks-header');
        const header = document.getElementById('nav-header');
        if (!header) return;

        if (bookmarkedMessages.size === 0 || cachedPrompts.length < 6) {
            if (bookmarksContainer) bookmarksContainer.remove();
            return;
        }

        if (!bookmarksContainer) {
            bookmarksContainer = document.createElement('div');
            bookmarksContainer.id = 'nav-bookmarks-header';
            bookmarksContainer.className = 'nav-bookmarks-container';
            bookmarksContainer.style.top = header.offsetHeight + 'px';
            header.parentNode.insertBefore(bookmarksContainer, header.nextSibling);
        }

        while (bookmarksContainer.firstChild) bookmarksContainer.removeChild(bookmarksContainer.firstChild);

        Array.from(bookmarkedMessages).sort((a, b) => a - b).forEach(index => {
            const bookmarkItem = document.createElement('span');
            bookmarkItem.className = 'nav-bookmark-item';
            bookmarkItem.textContent = index;
            if (index === activeMessageIndex) bookmarkItem.classList.add('active');
            bookmarkItem.addEventListener('click', () => navigateToMessage(index));
            bookmarksContainer.appendChild(bookmarkItem);
        });
    }

    function updateMessageCounter() {
        const counterSpan = document.getElementById('nav-message-counter');
        if (counterSpan) {
            const current = activeMessageIndex > 0 ? activeMessageIndex : (cachedPrompts.length > 0 ? 1 : 0);
            counterSpan.textContent = `${current}/${cachedPrompts.length}`;
        }
    }

    function navigateToMessage(messageIndex) {
        const targetElement = findPromptElementByIndex(messageIndex - 1);
        if (!targetElement) return;

        const list = document.getElementById('nav-list');
        if (list) {
            list.querySelectorAll('.nav-list-item').forEach(li => li.classList.remove('active'));
            const targetItem = list.children[messageIndex - 1];
            if (targetItem) {
                targetItem.classList.add('active');
                targetItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
            }
        }

        activeMessageIndex = messageIndex;
        updateMessageCounter();
        updateBookmarksHeader();

        if (CURRENT_SITE.useClick) {
            targetElement.click();
        } else {
            targetElement.scrollIntoView({ behavior: 'instant', block: 'start' });
        }

        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    targetElement.classList.add('nav-blink-active');
                    setTimeout(() => targetElement.classList.remove('nav-blink-active'), 2000);
                    observer.unobserve(targetElement);
                }
            });
        }, { threshold: 0.5 });
        observer.observe(targetElement);
    }

    function createContainer() {
        let container = document.getElementById('message-nav');
        if (!container) {
            container = document.createElement('div');
            container.id = 'message-nav';

            const header = document.createElement('div');
            header.id = 'nav-header';

            const navButtonsContainer = document.createElement('div');
            navButtonsContainer.id = 'nav-buttons-group';

            const firstBtn = document.createElement('button');
            firstBtn.id = 'nav-btn-first';
            firstBtn.className = 'nav-control-btn';
            firstBtn.textContent = '|◀';
            firstBtn.addEventListener('click', (e) => { e.stopPropagation(); if (cachedPrompts.length > 0) navigateToMessage(1); });

            const prevBtn = document.createElement('button');
            prevBtn.id = 'nav-btn-prev';
            prevBtn.className = 'nav-control-btn';
            prevBtn.textContent = '◀';
            prevBtn.addEventListener('click', (e) => { e.stopPropagation(); if (cachedPrompts.length > 0 && activeMessageIndex > 1) navigateToMessage(activeMessageIndex - 1); });

            const messageCounter = document.createElement('span');
            messageCounter.id = 'nav-message-counter';

            const nextBtn = document.createElement('button');
            nextBtn.id = 'nav-btn-next';
            nextBtn.className = 'nav-control-btn';
            nextBtn.textContent = '▶';
            nextBtn.addEventListener('click', (e) => { e.stopPropagation(); if (cachedPrompts.length > 0 && activeMessageIndex < cachedPrompts.length) navigateToMessage(activeMessageIndex + 1); });

            const lastBtn = document.createElement('button');
            lastBtn.id = 'nav-btn-last';
            lastBtn.className = 'nav-control-btn';
            lastBtn.textContent = '▶|';
            lastBtn.addEventListener('click', (e) => { e.stopPropagation(); if (cachedPrompts.length > 0) navigateToMessage(cachedPrompts.length); });

            navButtonsContainer.appendChild(firstBtn);
            navButtonsContainer.appendChild(prevBtn);
            navButtonsContainer.appendChild(messageCounter);
            navButtonsContainer.appendChild(nextBtn);
            navButtonsContainer.appendChild(lastBtn);

            const closeBtn = document.createElement('button');
            closeBtn.id = 'nav-btn-close';
            closeBtn.textContent = '✕';

            header.appendChild(navButtonsContainer);
            header.appendChild(closeBtn);

            const content = document.createElement('div');
            content.id = 'message-nav-content';

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

            closeBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                container.style.display = 'none';
                isClosedByUser = true;
                updateBodyClassForLayout();
            });

            updateBodyClassForLayout();
        }
        return container;
    }

    function getVersionInfo(container, vc) {
        if (!container || !vc) return null;
        const versionText = container.querySelector(vc.versionText);
        if (!versionText) return null;
        const text = versionText.textContent.trim();
        let match = text.match(/(\d+)\D+(\d+)/);
        let currentVersion, totalVersions;
        if (match) {
            currentVersion = parseInt(match[1]);
            totalVersions = parseInt(match[2]);
        } else if (text.length >= 2 && /^\d+$/.test(text)) {
            currentVersion = parseInt(text[0]);
            totalVersions = parseInt(text.slice(1));
        } else {
            return null;
        }
        const prevButton = container.querySelector(vc.prevButton);
        const nextButton = container.querySelector(vc.nextButton);
        return { currentVersion, totalVersions, prevButton, nextButton, hasMultipleVersions: totalVersions > 1 };
    }

    function findUserPrompts() {
        if (CURRENT_SITE.customFinder) return CURRENT_SITE.customFinder();

        let prompts = [];
        if (!CURRENT_SITE.userMessage || !CURRENT_SITE.userMessage.container) return prompts;

        const userElements = Array.from(document.querySelectorAll(CURRENT_SITE.userMessage.container));
        let containerElements = [];

        if (CURRENT_SITE.versionControl && CURRENT_SITE.versionControl.container) {
            containerElements = Array.from(document.querySelectorAll(CURRENT_SITE.versionControl.container));
        }

        if (CURRENT_SITE.reverse) {
            userElements.reverse();
            containerElements.reverse();
        }

        userElements.forEach((container, index) => {
            let text = "";
            const msgConfig = CURRENT_SITE.userMessage;
            const hasChildSelectors = msgConfig.file || msgConfig.text || msgConfig.image;

            if (hasChildSelectors) {
                const fileEl = msgConfig.file ? container.querySelector(msgConfig.file) : null;
                const msgEl = msgConfig.text ? container.querySelector(msgConfig.text) : null;
                const imageEl = msgConfig.image ? container.querySelector(msgConfig.image) : null;

                let fileTypeEl = null;
                let fileNameEl = null;
                let fileText = "";

                if (fileEl) {
                    const ariaLabel = fileEl.getAttribute('aria-label');
                    if (ariaLabel) {
                        fileText = ariaLabel.trim();
                    } else {
                        if (msgConfig.fileType) fileTypeEl = fileEl.querySelector(msgConfig.fileType);
                        if (msgConfig.fileName) fileNameEl = fileEl.querySelector(msgConfig.fileName);
                        fileText = fileNameEl ? fileNameEl.textContent.trim() : fileEl.textContent.trim();
                    }
                }

                const msgText = msgEl ? msgEl.textContent.trim() : "";
                let fileExt = "";
                if (fileTypeEl) {
                    fileExt = fileTypeEl.textContent.trim().toLowerCase();
                } else if (fileText) {
                    const m = fileText.match(/\.([a-z0-9]+)[^a-z0-9]*$/i);
                    fileExt = m ? m[1].toLowerCase() : "";
                }

                const imageExtensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif', '.bmp'];
                const isImageFile = imageExtensions.some(ext => fileText.toLowerCase().endsWith(ext)) ||
                                    imageExtensions.some(ext => ext.slice(1) === fileExt);
                const hasRealImage = !!imageEl || isImageFile;
                const hasDocument = (fileText && !isImageFile) || (fileExt && !isImageFile);
                const fileTag = fileExt ? `[${fileExt}]` : "[file]";
                const combinedTag = fileExt ? `[${fileExt}+img]` : "[file+img]";

                if (msgText) {
                    if (hasRealImage && !hasDocument)      text = `[img] ${msgText}`;
                    else if (hasDocument && !hasRealImage) text = `${fileTag} ${msgText}`;
                    else if (hasRealImage && hasDocument)  text = `${combinedTag} ${msgText}`;
                    else                                   text = msgText;
                } else {
                    if (hasRealImage && !hasDocument)      text = `[img] ${fileText || ""}`.trim() || "[img]";
                    else if (hasDocument && !hasRealImage) text = `${fileTag} ${fileText}`;
                    else if (hasRealImage && hasDocument)  text = `${combinedTag} ${fileText}`;
                    else                                   text = "[file]";
                }
            } else {
                text = container.textContent.trim();
                if (!text && (container.querySelector('img') || container.querySelector('canvas') || container.querySelector('svg'))) {
                    text = "[img]";
                }
            }

            if (text) {
                const promptData = { element: container, text };
                if (CURRENT_SITE.versionControl && containerElements[index]) {
                    const versionInfo = getVersionInfo(containerElements[index], CURRENT_SITE.versionControl);
                    if (versionInfo) promptData.versionInfo = versionInfo;
                }
                prompts.push(promptData);
            }
        });

        return prompts;
    }

    function findPromptElementByIndex(targetIndex) {
        if (CURRENT_SITE.customFinder) {
            const prompts = CURRENT_SITE.customFinder();
            return prompts[targetIndex] ? prompts[targetIndex].element : null;
        }
        if (!CURRENT_SITE.userMessage || !CURRENT_SITE.userMessage.container) return null;
        const elements = Array.from(document.querySelectorAll(CURRENT_SITE.userMessage.container));
        if (CURRENT_SITE.reverse) elements.reverse();
        return elements[targetIndex] || null;
    }

    function createListItem(prompt, index) {
        const listItem = document.createElement('li');
        listItem.className = 'nav-list-item';
        if (index === activeMessageIndex) listItem.classList.add('active');

        const preview = prompt.text.length > 80 ? prompt.text.slice(0, 80) + '...' : prompt.text;

        const numberContainer = document.createElement('span');
        numberContainer.className = 'nav-item-number';
        if (bookmarkedMessages.has(index)) {
            numberContainer.textContent = `${index}`;
            numberContainer.classList.add('bookmarked');
        } else {
            numberContainer.textContent = `${index}.`;
        }
        numberContainer.addEventListener('click', (e) => { e.stopPropagation(); toggleBookmark(index); });

        const textSpan = document.createElement('span');
        textSpan.textContent = ` ${preview}`;
        listItem.appendChild(numberContainer);
        listItem.appendChild(textSpan);

        if (prompt.versionInfo && prompt.versionInfo.hasMultipleVersions) {
            const versionControls = document.createElement('span');
            versionControls.className = 'nav-version-controls';

            const prevVersionBtn = document.createElement('button');
            prevVersionBtn.className = 'nav-version-btn';
            prevVersionBtn.textContent = '◀';
            prevVersionBtn.disabled = !prompt.versionInfo.prevButton || prompt.versionInfo.prevButton.disabled;
            prevVersionBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                if (prompt.versionInfo.prevButton && !prompt.versionInfo.prevButton.disabled) {
                    prompt.versionInfo.prevButton.click();
                    setTimeout(() => updateMessageList(true), 300);
                }
            });

            const versionText = document.createElement('span');
            versionText.className = 'nav-version-text';
            versionText.textContent = `${prompt.versionInfo.currentVersion}/${prompt.versionInfo.totalVersions}`;

            const nextVersionBtn = document.createElement('button');
            nextVersionBtn.className = 'nav-version-btn';
            nextVersionBtn.textContent = '▶';
            nextVersionBtn.disabled = !prompt.versionInfo.nextButton || prompt.versionInfo.nextButton.disabled;
            nextVersionBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                if (prompt.versionInfo.nextButton && !prompt.versionInfo.nextButton.disabled) {
                    prompt.versionInfo.nextButton.click();
                    setTimeout(() => updateMessageList(true), 300);
                }
            });

            versionControls.appendChild(prevVersionBtn);
            versionControls.appendChild(versionText);
            versionControls.appendChild(nextVersionBtn);
            listItem.appendChild(versionControls);
        }

        textSpan.addEventListener('click', () => navigateToMessage(index));
        return listItem;
    }

    function updateMessageList(forceUpdate = false) {
        const currentUrl = window.location.href;
        let container = document.getElementById('message-nav');

        if (currentUrl !== lastUrl) {
            lastUrl = currentUrl;
            lastPromptsContent = "";
            activeMessageIndex = -1;
            cachedPrompts = [];
            bookmarkedMessages.clear();
            loadBookmarks();
            isClosedByUser = false;
            forceUpdate = true;
        }

        const shouldShow = hasUserMessages();
        updateShiftStyles(shouldShow);

        if (!shouldShow || isClosedByUser) {
            if (container) container.style.display = 'none';
            document.body.classList.remove('navigator-expanded');
            updateBodyClassForLayout();
            return;
        }

        const activeContainer = createContainer();
        activeContainer.style.display = 'block';
        updateBodyClassForLayout();

        const content = document.getElementById('message-nav-content');
        if (!content) return;
        let list = document.getElementById('nav-list');
        if (!list) {
            list = document.createElement('ul');
            list.id = 'nav-list';
            content.appendChild(list);
        }

        const prompts = findUserPrompts();
        const currentPromptsContent = prompts.map(p => p.text).join('|');
        if (!forceUpdate && currentPromptsContent === lastPromptsContent) return;

        lastPromptsContent = currentPromptsContent;
        cachedPrompts = prompts;

        while (list.firstChild) list.removeChild(list.firstChild);
        prompts.forEach((prompt, index) => list.appendChild(createListItem(prompt, index + 1)));

        updateMessageCounter();
        updateBookmarksHeader();
    }

    function startUrlWatcher() {
        if (!CURRENT_SITE.fastUpdate) return;
        if (urlCheckInterval) clearInterval(urlCheckInterval);
        urlCheckInterval = setInterval(() => {
            if (window.location.href !== lastUrl) updateMessageList(true);
        }, 300);
    }

    function observeConversation() {
        if (conversationObserver) conversationObserver.disconnect();
        const debounceTime = CURRENT_SITE.debounceTime || DEBOUNCE_TIME;
        conversationObserver = new MutationObserver(() => {
            clearTimeout(window.navigatorUpdateTimeout);
            window.navigatorUpdateTimeout = setTimeout(() => updateMessageList(), debounceTime);
        });
        conversationObserver.observe(document.body, { childList: true, subtree: true });

        window.addEventListener('popstate', () => {
            lastPromptsContent = ""; cachedPrompts = []; updateMessageList(true);
        });

        const originalPushState = history.pushState;
        history.pushState = function () {
            originalPushState.apply(this, arguments);
            lastPromptsContent = ""; cachedPrompts = [];
            setTimeout(() => updateMessageList(true), 100);
        };
    }

    injectStyles();
    setTimeout(() => {
        cleanupExpiredBookmarks();
        loadBookmarks();
        updateMessageList(true);
        startUrlWatcher();
    }, 1000);
    observeConversation();
})();