Displays a floating container on the right with every message you sent in the current conversation.
当前为
// ==UserScript==
// @name ChatGPT Conversation Navigator (Advanced)
// @namespace https://greasyfork.org/users/1427520
// @version 1.1
// @description Displays a floating container on the right with every message you sent in the current conversation.
// @author Bạn & Bui Quoc Dung
// @match https://chatgpt.com/*
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @icon https://i.ibb.co/jZ3HpwPk/pngwing-com.png
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// --- CSS for moving layout to left (will be conditional) ---
const 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: auto !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; /* Allow full width when left-aligned */
}
body.navigator-expanded div.ProseMirror {
margin-left: 0 !important;
margin-right: auto !important;
}
/* Giới hạn chiều rộng nếu muốn, nhưng chỉ khi navigator-expanded */
body.navigator-expanded .xl\\:max-w-\\[48rem\\] {
width: 800px !important; /* Or your preferred width */
max-width: 100% !important;
}
`;
GM_addStyle(moveLeftStyle); // Add the styles, they will only apply when body has 'navigator-expanded' class
// --- Conversation Navigator Logic ---
let chatID = ''; // To track conversation changes
let userMsgCounter = 0;
// Function to apply or remove the 'navigator-expanded' class from body
function updateBodyClassForLayout() {
const container = document.getElementById('chatgpt-message-nav');
const content = document.getElementById('chatgpt-message-nav-content');
if (container && container.style.display !== 'none' && content && content.style.display !== 'none') {
document.body.classList.add('navigator-expanded');
} else {
document.body.classList.remove('navigator-expanded');
}
}
function createContainer() {
let container = document.getElementById('chatgpt-message-nav');
if (!container) {
container = document.createElement('div');
container.id = 'chatgpt-message-nav';
container.style.position = 'fixed';
container.style.top = '60px';
container.style.right = '20px';
container.style.width = '250px';
container.style.maxHeight = '80vh';
container.style.overflowY = 'auto';
container.className = "text-token-text-primary bg-token-main-surface-primary rounded-lg shadow-lg";
container.style.zIndex = '9999'; // Increased z-index
container.style.borderRadius = '8px';
container.style.boxShadow = '0px 4px 10px rgba(0, 0, 0, 0.3)';
container.style.fontSize = '14px';
container.style.transition = 'width 0.3s, padding 0.3s, opacity 0.3s, transform 0.3s'; // Added opacity and transform
const header = document.createElement('div');
header.id = 'chatgpt-message-nav-header';
header.style.display = 'flex';
header.style.alignItems = 'center';
header.style.justifyContent = 'space-between';
header.style.padding = '10px';
header.style.paddingTop = '15px';
header.style.cursor = 'pointer';
header.style.position = 'sticky';
header.style.top = '-7px'; // Adjusted slightly
header.style.background = 'inherit';
header.style.zIndex = '1';
const title = document.createElement('div');
title.id = 'chatgpt-message-nav-title';
title.style.fontWeight = 'bold';
title.innerText = 'Your Prompts';
const toggleBtn = document.createElement('button');
toggleBtn.id = 'chatgpt-message-nav-toggle';
toggleBtn.style.background = 'none';
toggleBtn.style.border = 'none';
toggleBtn.style.color = 'currentColor';
toggleBtn.style.fontSize = '16px';
toggleBtn.style.cursor = 'pointer';
toggleBtn.innerHTML = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="icon-md text-token-text-primary"><path fill-rule="evenodd" clip-rule="evenodd" d="M12 21C11.7348 21 11.4804 20.8946 11.2929 20.7071L4.29289 13.7071C3.90237 13.3166 3.90237 12.6834 4.29289 12.2929C4.68342 11.9024 5.31658 11.9024 5.70711 12.2929L11 17.5858V4C11 3.44772 11.4477 3 12 3C12.5523 3 13 3.44772 13 4V17.5858L18.2929 12.2929C18.6834 11.9024 19.3166 11.9024 19.7071 12.2929C20.0976 12.6834 20.0976 13.3166 19.7071 13.7071L12.7071 20.7071C12.5196 20.8946 12.2652 21 12 21Z" fill="currentColor"></path></svg>`;
toggleBtn.style.rotate = '-90deg';
toggleBtn.style.transition = 'rotate 0.3s';
header.appendChild(title);
header.appendChild(toggleBtn);
const content = document.createElement('div');
content.id = 'chatgpt-message-nav-content';
content.style.padding = '10px';
content.style.paddingTop = '0px';
container.appendChild(header);
container.appendChild(content);
document.body.appendChild(container);
// Using GM_getValue for persistent state
const collapsed = GM_getValue('chatgptMessageNavCollapsed', false);
if (collapsed) {
content.style.display = 'none';
container.style.width = 'min-content';
container.style.padding = '5px';
title.style.display = 'none';
toggleBtn.style.rotate = '90deg';
header.style.paddingTop = '10px';
}
toggleBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (content.style.display === 'none') {
content.style.display = 'block';
container.style.width = '250px';
container.style.padding = '7px'; // Original padding from navigator
title.style.display = 'block';
toggleBtn.style.rotate = '-90deg';
header.style.paddingTop = '15px';
GM_setValue('chatgptMessageNavCollapsed', false);
} else {
content.style.display = 'none';
container.style.width = 'min-content';
container.style.padding = '5px';
title.style.display = 'none';
toggleBtn.style.rotate = '90deg';
header.style.paddingTop = '10px';
GM_setValue('chatgptMessageNavCollapsed', true);
}
updateBodyClassForLayout(); // Update layout on toggle
});
updateBodyClassForLayout(); // Initial check
}
return container;
}
function assignIdToMessage(msgElem) {
if (!msgElem.id) {
userMsgCounter++;
msgElem.id = 'user-msg-' + userMsgCounter;
}
}
function createListItem(msgElem) {
const text = msgElem.innerText.trim();
let preview = text.length > 80 ? text.slice(0, 80) + '...' : text;
const messageWithPrefix = `* ${preview}`;
const listItem = document.createElement('li');
listItem.style.cursor = 'pointer';
listItem.style.padding = '5px 10px';
listItem.style.marginTop = '5px';
listItem.style.borderRadius = '10px';
listItem.style.borderBottom = '1px solid var(--main-surface-primary-inverse)';
listItem.style.transition = 'background 0.2s';
listItem.addEventListener('mouseenter', () => listItem.style.background = '#c5c5c54d');
listItem.addEventListener('mouseleave', () => listItem.style.background = 'transparent');
listItem.style.whiteSpace = 'normal';
listItem.style.overflow = 'hidden';
listItem.style.textOverflow = 'ellipsis';
listItem.innerHTML = messageWithPrefix;
listItem.addEventListener('click', () => {
const target = document.getElementById(msgElem.id);
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
return listItem;
}
function updateMessageList() {
const container = createContainer(); // Ensures container exists
const content = document.getElementById('chatgpt-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('div[data-message-author-role="user"]');
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 getChatID() {
const chatURL = window.location.pathname;
return chatURL.includes('/c/') ? chatURL.split('/c/')[1] : 'global';
}
let conversationObserver = null;
function observeConversation() {
if (conversationObserver) {
conversationObserver.disconnect(); // Disconnect previous observer if any
}
const mainElem = document.querySelector('main');
if (!mainElem) return;
conversationObserver = new MutationObserver(() => {
updateMessageList();
});
conversationObserver.observe(mainElem, { childList: true, subtree: true });
}
function toggleContainerVisibility() {
const container = document.getElementById('chatgpt-message-nav');
const isChatPage = window.location.pathname.startsWith('/c/');
if (container) {
container.style.display = isChatPage ? 'block' : 'none';
}
updateBodyClassForLayout(); // Update layout when container visibility changes
}
function initializeOrReinitialize() {
toggleContainerVisibility(); // Handles showing/hiding container and body class
if (window.location.pathname.startsWith('/c/')) {
createContainer(); // Ensure container is there for chat pages
chatID = getChatID();
userMsgCounter = 0; // Reset counter for new/reloaded chat
const content = document.getElementById('chatgpt-message-nav-content');
if (content) {
const list = content.querySelector('ul');
if(list) list.innerHTML = ''; // Clear list for new chat
}
updateMessageList(); // Initial population
observeConversation();
} else {
if (conversationObserver) {
conversationObserver.disconnect(); // Stop observing if not on a chat page
}
}
updateBodyClassForLayout(); // Ensure layout is correct based on new state
}
function waitForChatToLoad() {
const interval = setInterval(() => {
const mainElem = document.querySelector('main');
if (mainElem && mainElem.querySelector('div[data-message-author-role]')) {
clearInterval(interval);
initializeOrReinitialize();
} else if (mainElem && !window.location.pathname.startsWith('/c/')) { // Non-chat page but main exists
clearInterval(interval);
initializeOrReinitialize();
}
}, 500);
}
waitForChatToLoad(); // Initial load
let lastUrl = location.href;
const urlObserver = new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
waitForChatToLoad();
}
});
const pageChangeTargetNode = document.body; // Or a more specific element if known
const pageChangeConfig = { childList: true, subtree: true };
urlObserver.observe(pageChangeTargetNode, pageChangeConfig);
})();