您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Displays a list of user and assistant responses in ChatGPT conversations for quick navigation.
// ==UserScript== // @name ChatGPT Message Navigator // @namespace https://violentmonkey.github.io/ // @version 1.1 // @description Displays a list of user and assistant responses in ChatGPT conversations for quick navigation. // @author Bui Quoc Dung // @match https://chatgpt.com/* // @grant none // @license MIT // ==/UserScript== (function () { "use strict"; // If panel already exists, do nothing if (document.getElementById("toc-panel") || document.getElementById("toc-handle")) { return; } // --- Insert CSS with dark mode support --- const css = document.createElement("style"); css.textContent = /* Panel */ `#toc-panel { position: fixed; top: 0; right: 0; width: 280px; height: 100%; background: #fafafa; box-shadow: -4px 0 8px rgba(0,0,0,0.1); font-family: sans-serif; font-size: 0.8rem; border-left: 1px solid #ddd; display: flex; flex-direction: column; z-index: 9998; visibility: hidden; } #toc-panel.visible { visibility: visible; } #toc-header { padding: 6px 10px; background: #ddd; border-bottom: 1px solid #ccc; font-weight: bold; flex-shrink: 0; } #toc-list { list-style: none; flex: 1; overflow-y: auto; margin: 0; padding: 6px; } #toc-list li { padding: 4px; cursor: pointer; border-radius: 3px; transition: background-color 0.2s; } #toc-list li:hover { background: #f0f0f0; } #toc-handle { position: fixed; top: 50%; right: 0; transform: translateY(-50%); width: 30px; height: 80px; background: #ccc; display: flex; align-items: center; justify-content: center; writing-mode: vertical-rl; text-orientation: mixed; cursor: pointer; font-weight: bold; user-select: none; z-index: 9999; transition: background 0.2s; } #toc-handle:hover { background: #bbb; } @keyframes highlightFade { 0% { background-color: #fffa99; } 100% { background-color: transparent; } } .toc-highlight { animation: highlightFade 1.5s forwards; } @media (prefers-color-scheme: dark) { #toc-panel { background: #333; border-left: 1px solid #555; box-shadow: -4px 0 8px rgba(0,0,0,0.7); } #toc-header { background: #555; border-bottom: 1px solid #666; color: #eee; } #toc-list li:hover { background: #444; } #toc-list { color: #eee; } #toc-handle { background: #555; color: #ddd; } #toc-handle:hover { background: #666; } }`; document.head.appendChild(css); // --- Create panel & handle --- const panel = document.createElement("div"); panel.id = "toc-panel"; panel.innerHTML = ` <div id="toc-header">Conversation TOC</div> <ul id="toc-list"></ul> `; document.body.appendChild(panel); const handle = document.createElement("div"); handle.id = "toc-handle"; handle.textContent = "TOC"; document.body.appendChild(handle); // Observed container, observer, etc. let chatContainer = null; let observer = null; let isScheduled = false; let timerId = null; // Debounce the TOC build to avoid high CPU usage on rapid changes function debounceBuildTOC() { if (isScheduled) return; isScheduled = true; timerId = setTimeout(function () { buildTOC(); isScheduled = false; }, 300); } // Build/refresh the TOC function buildTOC() { const list = document.getElementById("toc-list"); if (!list) return; list.innerHTML = ""; // Find conversation turns const articles = (chatContainer || document).querySelectorAll("article[data-testid^='conversation-turn-']"); if (!articles || articles.length === 0) { list.innerHTML = '<li style="opacity:0.7;font-style:italic;">Empty chat</li>'; return; } // Loop over turns for (let i = 0; i < articles.length; i++) { const art = articles[i]; const li = document.createElement("li"); // Check if AI (assistant) const sr = art.querySelector("h6.sr-only"); let isAI = false; if (sr && sr.textContent.includes("ChatGPT said:")) { isAI = true; li.textContent = "ChatGPT:"; // Get the assistant message const assistantMsg = art.querySelector('div[data-message-author-role="assistant"]'); const assistantText = assistantMsg?.textContent?.trim(); if (assistantText) { li.innerHTML = "<strong>ChatGPT: </strong>" + assistantText.slice(0, 100) + (assistantText.length > 100 ? "..." : ""); } } else { // Get the user message const userMsg = art.querySelector('div[data-message-author-role="user"]'); const userText = userMsg?.textContent?.trim(); if (userText) { const preview = userText.slice(0, 100); li.innerHTML = "<strong>You: </strong>" + preview + (userText.length > 100 ? "..." : ""); } else { li.textContent = ""; } } // On click: scroll to turn (function (turnElem) { li.addEventListener("click", function () { turnElem.scrollIntoView({behavior: "smooth", block: "start"}); }); })(art); list.appendChild(li); } } // Attach observer to new container if needed function attachObserver() { const c = document.querySelector("main#main") || document.querySelector(".chat-container") || null; if (c !== chatContainer) { chatContainer = c; if (observer) { observer.disconnect(); observer = null; } if (chatContainer) { observer = new MutationObserver(function () { debounceBuildTOC(); }); observer.observe(chatContainer, {childList: true, subtree: true}); buildTOC(); } } } // Attempt to attach on load attachObserver(); // Re-check every 2s in case container changes const reAttachInterval = setInterval(attachObserver, 2000); // Panel toggle handle.addEventListener("click", function () { panel.classList.toggle("visible"); }); })();