// AsheshDevelopment
// filename: ADK_ChatGPT_QoL_Buttons_Plus.user.js
// ==UserScript==
// @name 🔰 ADK - ChatGPT QoL Buttons Plus 🔰
// @namespace http://tampermonkey.net/
// @version 2.0.0
// @description Enhance your ChatGPT experience with custom buttons and a sleek popup interface
// @author Ashesh Development
// @match *://chatgpt.com/*
// @match *://chat.openai.com/*
// @icon https://media.chatgptautocontinue.com/images/icons/openai/black/icon48.png?07bf6b7
// @icon64 https://media.chatgptautocontinue.com/images/icons/openai/black/icon64.png?07bf6b7
// @grant GM_registerMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==
(function() {
'use strict';
const MAX_CUSTOM_BUTTONS = 10;
const style = document.createElement('style');
style.innerHTML = `
.navigation-buttons {
margin: 10px 0 0 14px;
text-align: center;
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.navigation-buttons button {
margin: 5px;
padding: 5px 15px;
font-size: 14px;
cursor: pointer;
background: #66d9ef;
color: #272822;
border: none;
border-radius: 5px;
transition: background 0.3s;
}
.navigation-buttons button:hover {
background: #a6e22e;
}
.author-note {
margin-top: 10px;
font-size: 12px;
color: black;
background: #D3A52D;
padding: 5px;
border-radius: 3px;
text-align: center;
}
.conversation-number {
position: absolute;
left: 18px;
top: 60px;
font-size: 16px;
font-weight: bold;
color: #f92672; /* Monokai pink */
z-index: 1;
background: rgba(39, 40, 34, 0.8); /* Monokai background with transparency */
padding: 9px 12px;
border-radius: 7px;
border: 1px solid #f92672; /* Monokai pink */
cursor: pointer;
}
.xl\\:max-w-\\[48rem\\] {
max-width: 100%;
}
/* Increase side scroll bar width */
[class^="react-scroll-to-bottom--"]::-webkit-scrollbar {
width: 20px;
}
/* Increase side scroll bar thumb and scroll speed */
[class^="react-scroll-to-bottom--"]::-webkit-scrollbar-thumb {
min-height: 5%;
background: #fd971f; /* Monokai orange */
}
/* 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;
color: lightgrey; /* Light text for contrast */
}
/* Bigger Copy Code button */
button.flex.gap-1.items-center {
width: 100px;
display: flex;
justify-content: center;
background: #D3A52D;
padding: 5px;
position: relative;
color: #000; /* Contrast text color */
}
.custom-buttons {
margin-left: 5px;
}
.custom-buttons button {
background: #ff6f61;
color: #fff;
}
.custom-buttons button:hover {
background: #ff856e;
}
.popup-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
}
.popup {
background: rgba(30, 30, 30, 0.95);
padding: 20px;
border-radius: 10px;
max-width: 600px;
width: 100%;
z-index: 1001;
color: #E0E0E0;
font-family: Arial, sans-serif;
}
.popup-header {
margin-bottom: 15px;
text-align: center;
}
.popup-header h2 {
margin: 0;
font-size: 22px;
color: #FFD700;
}
.popup-content {
display: grid;
grid-template-columns: 1fr 2fr 1fr auto;
gap: 10px;
align-items: center;
}
.popup-content label {
grid-column: span 4;
text-align: center;
font-size: 16px;
margin-bottom: 10px;
color: #F3F99D;
}
.popup-content input, .popup-content select {
background: rgba(0, 0, 0, 0.2);
color: #E0E0E0;
border: 1px solid #BD93F9;
border-radius: 5px;
padding: 5px;
}
.popup-content button.delete-button {
background-color: #FF5C57;
color: #E0E0E0;
border: none;
border-radius: 5px;
cursor: pointer;
padding: 5px;
text-align: center;
transition: background-color 0.3s;
}
.popup-content button.delete-button:hover {
background-color: #FF6E67;
}
.popup-footer {
margin-top: 20px;
text-align: center;
display: flex;
justify-content: center;
gap: 10px;
}
.popup-footer button {
padding: 10px 20px;
font-size: 16px;
background-color: #57C7FF;
color: #272822;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
.popup-footer button:hover {
background-color: #69FF94;
}
.popup-footer button.close-button {
background-color: #FF9F43;
}
`;
document.head.appendChild(style);
function addNavigationButtons() {
const targetDiv = document.querySelector('.relative.px-2.py-2.text-center.text-xs.text-token-text-secondary');
if (targetDiv) {
let authorNote = document.querySelector('.author-note');
if (!authorNote) {
authorNote = document.createElement('div');
authorNote.className = 'author-note';
authorNote.textContent = 'Script by Ashesh Development';
targetDiv.appendChild(authorNote);
}
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>
<button id="mdGitignoreBtn">MDs + .gitignore</button>
<button id="continueBtn">Continue</button>
<button id="fullCodeBtn">Full Code</button>
<button id="fullCodePlusBtn">Full Code+</button>
`;
targetDiv.appendChild(navContainer);
document.getElementById("nextBtn").addEventListener("click", function () {
navigateResponse(1);
});
document.getElementById("prevBtn").addEventListener("click", function () {
navigateResponse(-1);
});
document.getElementById("mdGitignoreBtn").addEventListener("click", function () {
sendMessage("Write an artistic README.md that fits the scripts function using elements and emojis and some vibrant colors, and an OPEN SOURCE LICENSE, and a .gitignore file.");
});
document.getElementById("continueBtn").addEventListener("click", function () {
sendMessage("continue");
});
document.getElementById("fullCodeBtn").addEventListener("click", function () {
sendMessage("Write the full script without the placeholders and no commented lines. Just write everything without extra line and no explanations. I want the script ready to copy and paste into my IDE and I need it production ready.");
});
document.getElementById("fullCodePlusBtn").addEventListener("click", function () {
sendMessage("Please write the full script without any placeholders, removing all comments except those necessary for documentation. The script should include all necessary imports, class definitions, methods, logic, and detailed debug logs colorized using the RETRASH color scheme. It must be very detailed, easy to understand, fully functional, complete, and ready to paste directly into my JetBrains Rider IDE. The script must be production-ready, following all specified guidelines, including proper documentation within the code. Ensure that nothing is omitted, and the script is fully complete from start to finish. Always indicate 'AsheshDevelopment' in the first line of the script as a comment and the filename also as a comment on the second line of the script. The debug logs should use the following RETRASH color palette.");
});
loadCustomButtons(navContainer);
}
} else {
console.error('Target div not found');
}
}
function sendMessage(message, autosend = true) {
const textarea = document.querySelector('textarea');
if (textarea) {
textarea.value = message;
const inputEvent = new Event('input', { bubbles: true });
textarea.dispatchEvent(inputEvent);
if (autosend) {
setTimeout(() => {
const keydownEvent = new KeyboardEvent('keydown', {
bubbles: true,
cancelable: true,
key: 'Enter',
code: 'Enter',
keyCode: 13
});
textarea.dispatchEvent(keydownEvent);
}, 250);
}
}
}
function navigateResponse(direction) {
const responses = document.querySelectorAll('[data-testid^="conversation-turn-"]');
let closestIndex = findClosestResponseIndex(responses);
closestIndex += direction;
if (closestIndex >= 0 && closestIndex < responses.length) {
responses[closestIndex].scrollIntoView({ behavior: "smooth" });
} else {
console.log('No more responses to navigate.');
}
}
function findClosestResponseIndex(responses) {
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;
}
setTimeout(() => {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
addNavigationButtons();
});
} else {
addNavigationButtons();
}
}, 2000);
setInterval(addNavigationButtons, 2000);
function refreshPage() {
location.reload();
}
function toggleLiveLineNumbers() {
const isLive = GM_getValue('liveLineNumbers', true);
GM_setValue('liveLineNumbers', !isLive);
refreshPage();
}
function registerMenu() {
GM_registerMenuCommand(
`Live line numbers: ${GM_getValue('liveLineNumbers', true) ? 'ON' : 'OFF'}`,
toggleLiveLineNumbers
);
GM_registerMenuCommand('Manage Custom Buttons', openCustomButtonForm);
}
function loadCustomButtons(navContainer) {
if (document.querySelector('.custom-buttons')) {
return;
}
const customButtons = GM_getValue('customButtons', []);
const customButtonContainer = document.createElement('div');
customButtonContainer.className = 'custom-buttons';
customButtons.forEach((button) => {
const customButton = document.createElement('button');
customButton.textContent = button.text;
customButton.style.margin = '5px';
customButton.style.padding = '5px 15px';
customButton.style.fontSize = '14px';
customButton.style.cursor = 'pointer';
customButton.style.background = '#ff6f61';
customButton.style.color = '#fff';
customButton.style.border = 'none';
customButton.style.borderRadius = '5px';
customButton.style.transition = 'background 0.3s';
customButton.addEventListener('click', function () {
sendMessage(button.message, button.autosend);
});
customButton.addEventListener('mouseover', function () {
customButton.style.background = '#ff856e';
});
customButton.addEventListener('mouseout', function () {
customButton.style.background = '#ff6f61';
});
customButtonContainer.appendChild(customButton);
});
navContainer.appendChild(customButtonContainer);
}
function openCustomButtonForm() {
const customButtons = GM_getValue('customButtons', []);
const overlay = document.createElement('div');
overlay.className = 'popup-overlay';
const popup = document.createElement('div');
popup.className = 'popup';
const header = document.createElement('div');
header.className = 'popup-header';
header.innerHTML = `
<h2>Manage Custom Buttons</h2>
`;
const content = document.createElement('div');
content.className = 'popup-content';
const formFields = customButtons.map((button, index) => `
<input type="text" value="${button.text}" data-index="${index}" class="button-text">
<input type="text" value="${button.message}" data-index="${index}" class="button-message">
<select data-index="${index}" class="button-autosend">
<option value="Y" ${button.autosend ? 'selected' : ''}>Y</option>
<option value="N" ${!button.autosend ? 'selected' : ''}>N</option>
</select>
<button class="delete-button" data-index="${index}">Delete</button>
`).join('');
content.innerHTML = `
<label>Button Configuration (Fill in the details below)</label>
${formFields}
<input type="text" placeholder="New Button Text" class="button-text">
<input type="text" placeholder="New Button Prompt" class="button-message">
<select class="button-autosend">
<option value="Y">Y</option>
<option value="N" selected>N</option>
</select>
<button class="delete-button">Delete</button>
`;
const footer = document.createElement('div');
footer.className = 'popup-footer';
footer.innerHTML = `
<button id="saveCustomButtons">Save</button>
<button id="closePopup" class="close-button">Close</button>
`;
popup.appendChild(header);
popup.appendChild(content);
popup.appendChild(footer);
overlay.appendChild(popup);
document.body.appendChild(overlay);
document.getElementById('closePopup').addEventListener('click', () => {
document.body.removeChild(overlay);
});
document.getElementById('saveCustomButtons').addEventListener('click', () => {
saveCustomButtons();
document.body.removeChild(overlay);
});
}
function saveCustomButtons() {
const newButtons = [];
const texts = document.querySelectorAll('.button-text');
const messages = document.querySelectorAll('.button-message');
const autosends = document.querySelectorAll('.button-autosend');
const deletes = document.querySelectorAll('.delete-button');
texts.forEach((input, index) => {
if (deletes[index] && deletes[index].textContent === 'Delete') {
return;
}
if (input.value && messages[index].value) {
newButtons.push({
text: input.value,
message: messages[index].value,
autosend: autosends[index].value === 'Y'
});
}
});
GM_setValue('customButtons', newButtons);
alert('Custom buttons saved.');
location.reload();
}
registerMenu();
const liveLineNumbers = GM_getValue('liveLineNumbers', true);
function createLineNumbersElement() {
const lineNumbersElement = document.createElement('div');
lineNumbersElement.classList.add('line-numbers-inline');
lineNumbersElement.style.display = 'flex';
lineNumbersElement.style.flexDirection = 'column';
lineNumbersElement.style.paddingTop = '0.07rem';
lineNumbersElement.style.textAlign = 'right';
lineNumbersElement.style.userSelect = 'none';
lineNumbersElement.style.pointerEvents = 'none';
lineNumbersElement.style.color = '#aaa';
lineNumbersElement.style.background = '#1e1e1e';
lineNumbersElement.style.lineHeight = '1.5';
lineNumbersElement.style.zIndex = '2';
lineNumbersElement.style.position = 'sticky';
lineNumbersElement.style.left = '0';
lineNumbersElement.style.minWidth = 'auto';
lineNumbersElement.style.transform = 'none';
return lineNumbersElement;
}
function updateLineNumbers(codeBlock, lineNumbersElement) {
const lines = codeBlock.textContent.split('\n');
lineNumbersElement.innerHTML = '';
const maxLineNumber = lines.length;
const digits = maxLineNumber.toString().length;
const minWidth = `${digits}ch`;
lineNumbersElement.style.minWidth = minWidth;
codeBlock.style.paddingLeft = "1ch";
lines.forEach((_, index) => {
const lineNumber = document.createElement('div');
lineNumber.style.lineHeight = '1.5';
lineNumber.style.margin = '0';
lineNumber.style.padding = '0';
lineNumber.textContent = (index + 1).toString();
lineNumbersElement.appendChild(lineNumber);
});
}
function addLineNumbersInline(container, useLiveUpdates) {
const codeBlock = container.querySelector('code');
if (!codeBlock || container.querySelector('.line-numbers-inline')) return;
container.style.position = 'relative';
container.style.display = 'flex';
container.style.alignItems = 'flex-start';
container.style.padding = '0';
container.style.margin = '0';
container.style.overflowX = 'auto';
container.style.overflowY = 'hidden';
const lineNumbersElement = createLineNumbersElement();
container.insertBefore(lineNumbersElement, codeBlock);
if (useLiveUpdates) {
updateLineNumbers(codeBlock, lineNumbersElement);
let updateScheduled = false;
const observer = new MutationObserver(() => {
if (!updateScheduled) {
updateScheduled = true;
requestAnimationFrame(() => {
updateLineNumbers(codeBlock, lineNumbersElement);
updateScheduled = false;
});
}
});
observer.observe(codeBlock, {
childList: true,
subtree: true,
characterData: true,
});
} else {
let updateTimeout;
function debouncedUpdate() {
clearTimeout(updateTimeout);
updateTimeout = setTimeout(() => {
updateLineNumbers(codeBlock, lineNumbersElement);
}, 500);
}
const observer = new MutationObserver(mutations => {
let shouldUpdate = false;
mutations.forEach(mutation => {
if (mutation.type === 'childList' || mutation.type === 'characterData') {
shouldUpdate = true;
}
});
if (shouldUpdate) {
debouncedUpdate();
}
});
observer.observe(codeBlock, {
childList: true,
subtree: true,
characterData: true,
});
debouncedUpdate();
}
}
function observeCodeBlocks(useLiveUpdates) {
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1) {
const codeContainers = node.querySelectorAll('div.overflow-y-auto');
codeContainers.forEach(container => addLineNumbersInline(container, useLiveUpdates));
}
});
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
document.querySelectorAll('div.overflow-y-auto').forEach(container => {
addLineNumbersInline(container, useLiveUpdates);
});
}
observeCodeBlocks(liveLineNumbers);
})();