Adds a toolbar with auto-scroll (including auto resending after 500 errrors), copy last message (adding bold and italics markdown), and remove last message.
// ==UserScript==
// @name CAI Toolbar
// @namespace https://sleazyfork.org/en/users/1033554-thud-butt
// @version 1.0
// @description Adds a toolbar with auto-scroll (including auto resending after 500 errrors), copy last message (adding bold and italics markdown), and remove last message.
// @author Thud Butt
// @match https://beta.character.ai/*
// @icon https://characterai.io/static/logo512.png
// @license MIT
// ==/UserScript==
'use strict';
const enabledColor = '#009933';
const disabledColor = '#ff9900';
// Keep consistent state between page refreshes, unless we're coming from a 500 refresh
let autoScrollEnabled = (sessionStorage.getItem('auto-scroll') !== null) ? JSON.parse(sessionStorage.getItem('auto-scroll')) : false;
let autoScrollInterval = null;
loadToolbar();
setAutoScrollState(autoScrollEnabled);
function loadToolbar()
{
let customStyle = document.createElement('style');
customStyle.innerHTML = `
div.custom-toolbar {
background-color: rgb(37, 37, 37);
border-right: 1px solid rgb(15, 15, 15) !important;
border-bottom: 1px solid rgb(15, 15, 15) !important;
border-left: 1px solid rgb(15, 15, 15) !important;
border-radius: 0px 0px 10px 10px;
width: 200px;
height: 50px;
position: sticky;
top: 0;
margin-left: auto;
margin-right: auto;
z-index: 1000;
display: flex;
justify-content: space-around;
}
`;
document.body.prepend(customStyle);
// TOOLBAR
let divCustomToolbar = document.createElement('div');
divCustomToolbar.classList.add('custom-toolbar');
// AUTO SCROLL BUTTON
let buttonAutoScroll = document.createElement('button');
buttonAutoScroll.id = 'button-auto-scroll';
buttonAutoScroll.innerHTML = `
<svg width="30px" height="30px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="${autoScrollEnabled ? enabledColor : disabledColor}" d="M0 224c0 17.7 14.3 32 32 32s32-14.3 32-32c0-53 43-96 96-96H320v32c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l64-64c12.5-12.5 12.5-32.8 0-45.3l-64-64c-9.2-9.2-22.9-11.9-34.9-6.9S320 19.1 320 32V64H160C71.6 64 0 135.6 0 224zm512 64c0-17.7-14.3-32-32-32s-32 14.3-32 32c0 53-43 96-96 96H192V352c0-12.9-7.8-24.6-19.8-29.6s-25.7-2.2-34.9 6.9l-64 64c-12.5 12.5-12.5 32.8 0 45.3l64 64c9.2 9.2 22.9 11.9 34.9 6.9s19.8-16.6 19.8-29.6V448H352c88.4 0 160-71.6 160-160z"/></svg>
`;
buttonAutoScroll.type = 'button';
buttonAutoScroll.title = 'Auto-scroll'
buttonAutoScroll.classList.add('btn');
buttonAutoScroll.onclick = function()
{
autoScrollEnabled = !autoScrollEnabled;
setAutoScrollState(autoScrollEnabled)
};
divCustomToolbar.append(buttonAutoScroll);
document.body.prepend(divCustomToolbar);
// COPY LAST MESSAGE BUTTON
let buttonCopy = document.createElement('button');
buttonCopy.id = 'button-copy-last';
buttonCopy.innerHTML = `
<svg width="30px" height="30px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#ededed" d="M224 0c-35.3 0-64 28.7-64 64V288c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64H224zM64 160c-35.3 0-64 28.7-64 64V448c0 35.3 28.7 64 64 64H288c35.3 0 64-28.7 64-64V384H288v64H64V224h64V160H64z"/></svg>
`;
buttonCopy.type = 'button';
buttonCopy.title = 'Copy last message'
buttonCopy.classList.add('btn');
buttonCopy.onclick = function()
{
let lastMessage = document.querySelector('div.chatdisplay div.user-msg:last-of-type p').innerHTML;
lastMessage = lastMessage.replace(/<\/?em[^>]*>/g, '*');
lastMessage = lastMessage.replace(/<\/?strong[^>]*>/g, '**');
navigator.clipboard.writeText(lastMessage);
};
divCustomToolbar.append(buttonCopy);
// REMOVE LAST MESSAGE BUTTON
let buttonRemoveLast = document.createElement('button');
buttonRemoveLast.id = 'button-remove-last';
buttonRemoveLast.innerHTML = `
<svg width="30px" height="30px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#ededed" d="M258.7 57.4L25.4 290.7c-25 25-25 65.5 0 90.5l80 80c12 12 28.3 18.7 45.3 18.7H256h9.4H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H355.9L486.6 285.3c25-25 25-65.5 0-90.5L349.3 57.4c-25-25-65.5-25-90.5 0zM265.4 416H256l-105.4 0-80-80L195.3 211.3 332.7 348.7 265.4 416z"/></svg>
`;
buttonRemoveLast.type = 'button';
buttonRemoveLast.title = 'Remove last message'
buttonRemoveLast.classList.add('btn');
buttonRemoveLast.onclick = function()
{
document.querySelector('div.chattop span[data-toggle="dropdown"]').click();
document.querySelector('div.dropdown-menu.show button:nth-of-type(4n)').click();
document.querySelector('div.chatdisplay input[type="checkbox"]:last-of-type').click();
document.querySelector('div.chatfooter button.btn-danger').click();
};
divCustomToolbar.append(buttonRemoveLast);
document.body.prepend(divCustomToolbar);
}
// Autoscrolling
function startAutoScroll()
{
waitForElement("div[class='swiper-button-next']", 1000).then(function()
{
for(let step = 0; step < 5; step++)
{
arrowRight();
};
})
.catch(() => {});
// Clicks the Try Again button
waitForElement('.Toastify__toast--default', 1000).then(function()
{
document.querySelector('.Toastify__toast--default .btn-primary').click();
})
.catch(() => {});
// If there has been a 500 error, then we need to reload the page, remove our last message, and resend it.
waitForElement('.Toastify__toast--error', 1000).then(function()
{
document.getElementById('user-input').closest('div').querySelector('button:nth-child(2)').click();
})
.catch(() => {});
// Add message count
waitForElement('div.msg-row', 30000).then(function()
{
addNumber();
let messageNumberObserverRow = new MutationObserver(addNumber());
messageNumberObserverRow.observe(document.querySelector('div.swiper-wrapper'), {childList: true});
let messageNumberObserverCol = new MutationObserver(function()
{
messageNumberObserverRow.observe(document.querySelector('div.swiper-wrapper'), {childList: true});
});
messageNumberObserverCol.observe(document.querySelector('div.infinite-scroll-component'), {childList: true});
})
.catch(() => {});
}
function setAutoScrollState(state)
{
// Toggle the auto retry button
if(state)
{
document.querySelector('#button-auto-scroll path').setAttribute('fill', disabledColor);
clearInterval(autoScrollInterval);
}
else
{
document.querySelector('#button-auto-scroll path').setAttribute('fill', enabledColor);
autoScrollInterval = setInterval(startAutoScroll, 1000);
}
sessionStorage.setItem('auto-scroll', autoScrollEnabled);
}
// Timed MutationObserver
function waitForElement(querySelector, timeout)
{
return new Promise((resolve, reject) =>
{
let timer = false;
if(document.querySelectorAll(querySelector).length) return resolve();
const observer = new MutationObserver(() =>
{
if(document.querySelectorAll(querySelector).length)
{
observer.disconnect();
if(timer !== false) clearTimeout(timer);
return resolve();
}
});
observer.observe(document.body, {childList: true, subtree: true});
if(timeout)
{
timer = setTimeout(function()
{
observer.disconnect();
reject();
}, timeout);
}
});
}
// Arrow right and arrow left keypresses for "swiping"
function arrowRight()
{
document.dispatchEvent(new KeyboardEvent('keydown', {bubbles: true, key: 'ArrowRight'}));
}
function arrowLeft()
{
document.dispatchEvent(new KeyboardEvent('keydown', {bubbles: true, key: 'ArrowLeft'}));
}
function addNumber()
{
let messageNumber = document.querySelectorAll('.swiper-wrapper .swiper-slide .rounded .flex-column');
for (let i = 0; i < messageNumber.length; i++)
{
messageNumber[i].innerHTML = messageNumber[i].innerHTML.replace(messageNumber[i].innerHTML, 'c.AI | ' + (i + 1));
}
}