Greasy Fork is available in English.
Floating navigator for your prompts in conversations with bookmark feature. Applied for ChatGPT, Gemini, Aistudio, NotebookLM, Google search, Grok, Claude, Mistral, Deepseek, Kimi, Qwen, Z.ai, Chatglm, Ernie, Meta, 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.
当前为
// ==UserScript==
// @name AI Conversation Navigator
// @namespace http://greasyfork.icu
// @version 12.6
// @description Floating navigator for your prompts in conversations with bookmark feature. Applied for ChatGPT, Gemini, Aistudio, NotebookLM, Google search, Grok, Claude, Mistral, Deepseek, Kimi, Qwen, Z.ai, Chatglm, Ernie, Meta, 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://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
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const NAV_WIDTH = 250;
const DEBOUNCE_TIME = 500;
const AISTUDIO_DEBOUNCE_TIME = 200;
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) {
let text = btn.getAttribute('aria-label');
if (text) {
prompts.push({
element: btn,
text: text.trim()
});
}
}
});
return prompts;
}
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
};
}
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"]'
}
},
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,
},
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',
},
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;
const elements = document.querySelectorAll(CURRENT_SITE.userMessage.container);
return elements && elements.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 toggleBookmark(index) {
if (bookmarkedMessages.has(index)) {
bookmarkedMessages.delete(index);
} else {
bookmarkedMessages.add(index);
}
updateBookmarkVisuals();
updateBookmarksHeader();
}
function updateBookmarkVisuals() {
const content = document.getElementById('message-nav-content');
if (!content) return;
const list = content.querySelector('#nav-list');
if (!list) return;
const items = list.querySelectorAll('.nav-list-item');
items.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);
}
const sortedBookmarks = Array.from(bookmarkedMessages).sort((a, b) => a - b);
sortedBookmarks.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);
const total = cachedPrompts.length;
counterSpan.textContent = `${current}/${total}`;
}
}
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 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();
}
} else {
fileText = "";
}
const msgText = msgEl ? msgEl.textContent.trim() : "";
let fileExt = "";
if (fileTypeEl) {
fileExt = fileTypeEl.textContent.trim().toLowerCase();
} else if (fileText) {
const fileExtMatch = fileText.match(/\.([a-z0-9]+)[^a-z0-9]*$/i);
fileExt = fileExtMatch ? fileExtMatch[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);
let fileTag = fileExt ? `[${fileExt}]` : "[file]";
let 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();
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) => {
const listItem = createListItem(prompt, index + 1);
list.appendChild(listItem);
});
updateMessageCounter();
updateBookmarksHeader();
}
function startUrlWatcher() {
if (!CURRENT_SITE.fastUpdate) return;
if (urlCheckInterval) clearInterval(urlCheckInterval);
urlCheckInterval = setInterval(() => {
const currentUrl = window.location.href;
if (currentUrl !== 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(() => { updateMessageList(true); startUrlWatcher(); }, 1000);
observeConversation();
})();