It’s been a while since the last time I modded chatgpt and a lot has changed since then so it was time for an update. First, here’s what the mod looks like:
As a developer, I copy code a lot and the ChatGPT vanilla GUI is not great as the responses are lengthy and you need to scroll up every single time after a response in order to copy the code. Also, navigating to a previous response is a pain and often takes quite some time to find what you were looking for. And wouldn’t it be nice if you could hide/minify responses/conversations that were bad or obsolete?
These are the changes in the mod:
- Changes view to wide
- Move copy code button to bottom of code window
- Bigger copy button
- Previous/Next buttons to navigate to nearest previous/next conversation
- Display Conversation ID at each conversation
- Mark conversations by clicking the conversation ID once (red number)
- Or hide/minify conversations by clicking the conversation ID twice (grey number). Clicking it a 3rd time resets the view to normal.
(These changes are not persistent. When you refresh the page the marked or hidden conversations are back to normal)
And here’s a video of this mod:
Youtube video
The conversation ID’s actually start with number 2 in chatgpt, but for ease of use this has been changed to start with number 1.
Due to continuous updates by OpenAI, these changes may not stay relevant and may require script updates in order to keep the modifications functional.
Tested on Windows with Chrome and Edge.
Steps:
- Go to the Chrome web store and install Tampermonkey:
- Click the little puzzle icon in the right top of the browser and pin the black tampermonkey icon to make it visible.
- Click the Tampermonkey icon and select “New script”
- Copy/paste the script from here: https://pastebin.com/98v5i6Mv or from the code box below.
- Save the script, open the settings tab for the script and select document-end under the General settings.
- Open a chatgpt window and refresh the page. The changes should be visible immediately.
// ==UserScript==
// @name Navigation Script
// @namespace http://tampermonkey.net/
// @version 2024-06-10
// @description Add navigation buttons to move between responses and display conversation numbers
// @author You
// @match https://chatgpt.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=chatgpt.com
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Add CSS for padding and conversation numbers
const style = document.createElement('style');
style.innerHTML = `
[data-testid^="conversation-turn-"] {
padding-top: 50px !important;
position: relative;
}
.navigation-buttons {
position: fixed;
margin: -9px 0 0 14px;
}
.navigation-buttons button {
margin: 5px;
padding: 5px 30px;
font-size: 16px;
cursor: pointer;
background: mediumslateblue;
color: white;
border: none;
border-radius: 5px;
}
.navigation-buttons button:hover {
background: darkslateblue;
}
.conversation-number {
position: absolute;
left: 18px;
top: 60px;
font-size: 16px;
font-weight: bold;
color: white;
z-index: 1;
background: #000000;
padding: 9px 12px;
border-radius: 7px;
border: 1px solid white;
cursor: pointer;
}
.xl\\:max-w-\\[48rem\\] {
max-width: 100%;
}
/* Increase side scroll bar width */
[class^="react-scroll-to-bottom--"]::-webkit-scrollbar {
width: 40px;
}
/* Increase side scroll bar thumb and scroll speed */
[class^="react-scroll-to-bottom--"]::-webkit-scrollbar-thumb {
min-height: 10%;
background: darkslateblue;
}
/* Remove prompt suggestions */
button.btn.relative.btn-neutral.group.w-full.whitespace-nowrap.rounded-xl.text-left.text-gray-700.dark\\:text-gray-300.md\\:whitespace-normal {
display: none;
}
/* Move regenerate button under the prompt input */
.md\\:items-end {
align-items: flex-end;
position: absolute;
left: 0;
bottom: -45px;
}
/* Add red bg color to regenerate button */
button.btn.relative.btn-neutral.whitespace-nowrap.-z-0.border-0.md\\:border {
border-radius: 10px;
background: #ee0008;
border: 1px solid #ee0008;
color: #fff;
}
/* Regenerate button hover */
button.btn.relative.btn-neutral.whitespace-nowrap.-z-0.border-0.md\\:border:hover {
background: #9c1519;
border: 1px solid #9c1519;
transition: 0.25s;
}
/* Add "response" to regenerate button */
button.btn.relative.btn-neutral.whitespace-nowrap.-z-0.border-0.md\\:border .flex.w-full.gap-2.items-center.justify-center:after {
content: "last response";
}
/* Move top bar to bottom */
.dark.bg-gray-950.rounded-md.border-\\[0\\.5px\\].border-token-border-medium {
display: flex;
flex-direction: column;
height: 100%;
}
.overflow-y-auto.p-4 {
order: 1; /* Content comes first */
flex-grow: 1; /* Ensure content takes up available space */
}
.flex.items-center.relative.text-token-text-secondary.bg-token-main-surface-secondary.px-4.py-2.text-xs.font-sans.justify-between.rounded-t-md {
order: 2; /* Top bar comes second */
direction: rtl;
background: darkslateblue;
}
/* Bigger copy button */
button.flex.gap-1.items-center {
width: 200px;
display: flex;
justify-content: center;
background: indigo;
padding: 10px;
}
@media (min-width: 1024px) {
.ZnJ1aXRqdWljZQ .juice\\:lg\\:gap-6 {
gap: 1.5rem;
min-width: 100%;
}
}
`;
document.head.appendChild(style);
let responses = [];
// Function to find and log all elements with data-testid attribute
function logTestIdElements() {
responses = Array.from(document.querySelectorAll('[data-testid^="conversation-turn-"]'));
console.log('Total responses found:', responses.length);
responses.forEach((response, index) => {
console.log('Response', index, response.getAttribute('data-testid'));
addConversationNumber(response, index + 1); // Add conversation number
});
}
// Function to add conversation number to each response
function addConversationNumber(element, number) {
let numberElement = element.querySelector('.conversation-number');
if (!numberElement) {
numberElement = document.createElement('div');
numberElement.className = 'conversation-number';
numberElement.addEventListener('click', () => {
if (numberElement.style.color === 'red') {
numberElement.style.color = 'grey';
element.style.maxHeight = '110px';
element.style.overflow = 'hidden';
} else if (numberElement.style.color === 'grey') {
numberElement.style.color = 'white';
element.style.maxHeight = '';
element.style.overflow = '';
} else {
numberElement.style.color = 'red';
}
});
element.insertBefore(numberElement, element.firstChild);
}
numberElement.textContent = number;
}
// Function to add navigation buttons
function addNavigationButtons() {
const targetDiv = document.querySelector('.relative.px-2.py-2.text-center.text-xs.text-token-text-secondary.md\\:px-\\[60px\\]');
if (targetDiv) {
let navContainer = document.querySelector('.navigation-buttons');
if (!navContainer) {
navContainer = document.createElement('div');
navContainer.className = 'navigation-buttons';
navContainer.innerHTML = `
<button id="prevBtn">Prev.</button>
<button id="nextBtn">Next</button>
`;
targetDiv.insertBefore(navContainer, targetDiv.firstChild);
// Add event listeners to the buttons
document.getElementById("nextBtn").addEventListener("click", function() {
logTestIdElements(); // Fetch the latest responses
const closestIndex = findClosestResponseIndex();
if (closestIndex < responses.length - 1) {
const nextIndex = closestIndex + 1;
console.log('Navigating to:', responses[nextIndex].getAttribute('data-testid'));
responses[nextIndex].scrollIntoView({ behavior: "smooth" });
} else {
console.log('No more responses to navigate to.');
}
});
document.getElementById("prevBtn").addEventListener("click", function() {
logTestIdElements(); // Fetch the latest responses
const closestIndex = findClosestResponseIndex();
if (closestIndex > 0) {
const prevIndex = closestIndex - 1;
console.log('Navigating to:', responses[prevIndex].getAttribute('data-testid'));
responses[prevIndex].scrollIntoView({ behavior: "smooth" });
} else {
console.log('No previous responses to navigate to.');
}
});
}
} else {
console.error('Target div not found');
}
}
// Function to find the closest response based on the current scroll position
function findClosestResponseIndex() {
let closestIndex = 0;
let minDistance = Infinity;
const viewportTop = window.scrollY;
const viewportBottom = viewportTop + window.innerHeight;
responses.forEach((response, index) => {
const rect = response.getBoundingClientRect();
const responseTop = rect.top + window.scrollY;
const responseBottom = rect.bottom + window.scrollY;
const distance = Math.min(
Math.abs(viewportTop - responseTop),
Math.abs(viewportBottom - responseBottom)
);
if (distance < minDistance) {
minDistance = distance;
closestIndex = index;
}
});
return closestIndex;
}
// Function to initialize the script
function initialize() {
logTestIdElements();
addNavigationButtons();
}
// Wait for a short delay before running the script
setTimeout(() => {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
initialize();
}
}, 2000); // Adjust the delay time as needed
// Ensure buttons are reloaded when navigating to a new conversation
setInterval(addNavigationButtons, 2000);
})();
This script and embedded styling aren’t perfect, so feel free to modify it to your liking. Keep in mind that adding custom Javascript to Tampermonkey poses a potential risk if the source code contains malware. The script I’m sharing is safe and does nothing other than what’s listed above, but always check the source code if you copy something off the internet to make sure there’s no hidden features.