ChatGPT Conversation Navigator

Displays a floating container on the right with every message you sent in the current conversation. Clicking a message scrolls smoothly to it.

目前為 2025-03-15 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

作者
Mohamed Eltelb
評價
0 0 0
版本
1.4.2
建立日期
2025-03-15
更新日期
2025-03-15
尺寸
10.2 KB
授權條款
MIT
腳本執行於

// ==UserScript==
// @name ChatGPT Conversation Navigator
// @namespace https://greasyfork.org/en/users/1444872-tlbstation
// @author TLBSTATION
// @icon https://i.ibb.co/jZ3HpwPk/pngwing-com.png
// @version 1.4.2
// @description Displays a floating container on the right with every message you sent in the current conversation. Clicking a message scrolls smoothly to it.
// @match https://chatgpt.com/*
// @grant none
// @license MIT
// ==/UserScript==

(function () {
'use strict';

let chatID = ''; // To track conversation changes
let userMsgCounter = 0;

// Create (or retrieve) the floating container on the right
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 = '10px';
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 = '1';
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';

// Create header with title and toggle button
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';
header.style.background = 'inherit';
header.style.zIndex = '1';


const title = document.createElement('div');
title.id = 'chatgpt-message-nav-title';
title.innerText = 'Your Messages';
title.style.fontWeight = 'bold';

const toggleBtn = document.createElement('button');
toggleBtn.id = 'chatgpt-message-nav-toggle';
toggleBtn.style.background = 'none';
toggleBtn.style.border = 'none';
toggleBtn.style.color = 'white';
toggleBtn.style.fontSize = '16px';
toggleBtn.style.cursor = 'pointer';
// Default state: expanded, so show "<"
toggleBtn.innerHTML = ``;
toggleBtn.style.rotate = '-90deg';
toggleBtn.style.transition = 'rotate 0.3s';
header.appendChild(title);
header.appendChild(toggleBtn);

// Create content container for the list
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);

// Check if collapsed state is stored
const collapsed = localStorage.getItem('chatgptMessageNavCollapsed') === 'true';
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';
}

// Toggle functionality
toggleBtn.addEventListener('click', () => {
if (content.style.display === 'none') {
// Expand
content.style.display = 'block';
container.style.width = '250px';
container.style.padding = '7px';
title.style.display = 'block';
toggleBtn.style.rotate = '-90deg';
header.style.paddingTop = '15px';
localStorage.setItem('chatgptMessageNavCollapsed', 'false');
} else {
// Collapse
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';
localStorage.setItem('chatgptMessageNavCollapsed', 'true');
}
});
}
return container;
}

// Ensure each user message gets a unique ID
function assignIdToMessage(msgElem) {
if (!msgElem.id) {
userMsgCounter++;
msgElem.id = 'user-msg-' + userMsgCounter;
}
}

// Update the list in the floating container with all user messages
function updateMessageList() {
const container = createContainer();
const content = document.getElementById('chatgpt-message-nav-content');
if (!content) return;

// Preserve current scroll position
const prevScrollTop = content.scrollTop;

const list = content.querySelector('ul');
if (!list) {
// Create the list only once
const newList = document.createElement('ul');
newList.style.padding = '0';
newList.style.margin = '0';
newList.style.listStyle = 'none';
content.appendChild(newList);
}

// Get the existing message IDs to avoid duplicate entries
const existingIds = new Set([...content.querySelectorAll('li')].map(li => li.dataset.msgId));

// Select user messages
const userMessages = document.querySelectorAll('div[data-message-author-role="user"]');
userMessages.forEach(msgElem => {
assignIdToMessage(msgElem);
const text = msgElem.innerText.trim();

// Skip if already in the list
if (existingIds.has(msgElem.id)) return;

const listItem = document.createElement('li');
listItem.dataset.msgId = msgElem.id; // Store msg ID to prevent duplication
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.style.whiteSpace = 'nowrap';
listItem.style.overflow = 'hidden';
listItem.style.textOverflow = 'ellipsis';

listItem.addEventListener('mouseenter', () => {
listItem.style.background = '#c5c5c54d' ;
});
listItem.addEventListener('mouseleave', () => {
listItem.style.background = 'transparent';
});

listItem.textContent = text;

// Smooth scroll to message when clicked
listItem.addEventListener('click', () => {
const target = document.getElementById(msgElem.id);
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});

// Append new list item
content.querySelector('ul').appendChild(listItem);
});

// Restore scroll position after updating the list
content.scrollTop = prevScrollTop;
}


// Get the current conversation ID based on the URL
function getChatID() {
const chatURL = window.location.pathname;
return chatURL.includes('/c/') ? chatURL.split('/c/')[1] : 'global';
}

// Observe the conversation container for changes
function observeConversation() {
const mainElem = document.querySelector('main');
if (!mainElem) return;
const observer = new MutationObserver(() => {
updateMessageList();
});
observer.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';
}
}


// Wait for the conversation area to load
function waitForChat() {
const interval = setInterval(() => {
if (document.querySelector('main')) {
clearInterval(interval);
toggleContainerVisibility();
chatID = getChatID();
updateMessageList();
observeConversation();
}
}, 500);
}

waitForChat();

// Update when switching between conversations (for SPA navigation)
let lastUrl = location.href;
const urlObserver = new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
chatID = getChatID();
userMsgCounter = 0;
updateMessageList();
toggleContainerVisibility();
}
});

urlObserver.observe(document.body, { childList: true, subtree: true });
})();