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 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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 });
})();