// ==UserScript==
// @name Pre ChatGPT
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Handle multiple questions for ChatGPT
// @author Your Name
// @match https://chat.openai.com/*
// @license MIT
// @grant GM_addStyle
// ==/UserScript==
(function () {
'use strict';
// 添加 CSS 样式
GM_addStyle(`
#sidebar {
position: fixed;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 300px;
padding: 20px;
background-color: #fafafa;
border: 1px solid #ccc;
border-radius: 10px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease-in-out;
overflow: hidden;
}
#toggleSidebar {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
width: 30px;
height: 30px;
background: #fafafa;
border: 1px solid #ccc;
border-radius: 50%;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: left 0.3s, border 0.3s, background 0.3s;
display: flex;
align-items: center;
justify-content: center;
}
#sidebar.collapsed #sidebarContent {
display: none;
}
#sidebar.collapsed #toggleSidebar {
left: 15px;
}
#sidebarWrapper {
position: relative; /* New wrapper */
}
#toggleSidebar:hover {
border-color: #007BFF; /* Change border color on hover */
background: #e6e6e6; /* Change background color on hover */
}
#toggleSidebar svg {
height: 15px;
width: 15px;
transition: all 0.3s ease-in-out;
}
#sidebar.collapsed {
width: 60px;
}
#sidebar.collapsed #toggleSidebar {
left: 15px; /* Position it to the right when sidebar is collapsed */
}
#sidebar h2 {
text-align: center;
color: #333;
font-size: 1.4em;
padding-bottom: 10px;
border-bottom: 1px solid #ccc;
margin-bottom: 10px;
}
#sidebar textarea {
width: 100%;
height: 100px;
margin-bottom: 10px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
transition: border-color 0.3s;
resize: none;
}
#sidebar textarea:focus {
border-color: #007BFF;
outline: none;
}
#sidebar button {
width: 100%;
padding: 10px;
margin-bottom: 10px;
border: none;
border-radius: 5px;
color: white;
cursor: pointer;
transition: background-color 0.3s;
}
#submitQuestion {
background-color: #4CAF50;
}
#submitQuestion:hover {
background-color: #45a049;
}
#start {
background-color: #008CBA;
margin-bottom: 20px;
}
#start:hover {
background-color: #007B99;
}
#questionList {
max-height: 200px;
overflow-y: auto;
}
.question {
margin-bottom: 10px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
background-color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
transition: background-color 0.3s;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.question:hover {
background-color: #f0f0f0;
}
.question:before {
content: '❓';
margin-right: 4px;
}
.question.answered {
color: #aaa;
}
.question.answered:before {
content: '✅';
}
.question button {
margin-left: 0px;
background: none;
border: 1px solid #000; /* 新增此行 */
box-sizing: border-box; /* 新增此行 */
cursor: pointer;
transition: color 0.3s;
display: flex;
justify-content: center;
align-items: center;
}
.question button:hover {
color: #007BFF;
}
.question .button-container {
margin-left: auto;
display: flex;
gap: 0px;
}
.button-container button {
width: 24px; /* Increase the size of the button */
height: 24px; /* Increase the size of the button */
padding: 0;
border: 2px solid red; /* Use a more visible border */
}
.question button svg {
width: 18px;
height: 18px;
margin: auto;
pointer-events: none; /* Let click events pass through the SVG to the button */
}
.button-group {
display: flex;
justify-content: space-between;
}
.question-text {
flex-grow: 1;
flex-shrink: 1;
flex-basis: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border: none;
outline: none;
}
#settingSidebar {
position: fixed;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 300px;
padding: 20px;
background-color: #fafafa;
border: 1px solid #ccc;
border-radius: 10px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease-in-out;
overflow: hidden;
}
#settingSidebar h2 {
text-align: center;
color: #333;
font-size: 1.4em;
padding-bottom: 10px;
border-bottom: 1px solid #ccc;
margin-bottom: 10px;
}
.input-row {
margin-bottom: 10px;
}
.input-row label {
display: block;
margin-bottom: 5px;
color: #333;
font-weight: bold;
}
.input-row input[type="text"],
.input-row input[type="number"] {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
transition: border-color 0.3s;
font-size: 14px;
font-family: Arial, sans-serif;
}
.input-row input[type="text"]:focus,
.input-row input[type="number"]:focus {
border-color: #007BFF;
outline: none;
}
#runMode {
display: flex;
align-items: center;
}
#runMode input[type="radio"] {
margin-right: 10px;
}
#delayTime {
width: 80px;
margin-left: 10px;
padding: 5px;
border: 1px solid #ccc;
border-radius: 5px;
transition: border-color 0.3s;
width: 100px; /* 调整宽度为适当大小 */
text-align: right; /* 将文本右对齐 */
}
#delayTime:disabled {
background-color: #f0f0f0;
}
.clear-cache-btn {
padding: 10px 20px;
border: none;
border-radius: 5px;
background-color: #88c0d0;
color: white;
font-size: 15px;
cursor: pointer;
transition: all 0.3s ease;
outline: none;
box-shadow: 0px 5px 10px rgba(0,0,0,0.2);
}
.clear-cache-btn:active {
box-shadow: 0px 2px 5px rgba(0,0,0,0.2);
transform: translateY(3px);
}
.clear-cache-btn:hover {
background-color: #81a1c1;
}
.button-container1 {
display: flex;
justify-content: center;
align-items: center;
height: 30px;
}
`);
// 创建主侧边栏
function createMainSidebar() {
const sidebar = document.createElement('div');
sidebar.id = 'sidebar';
sidebar.innerHTML = `
<div id="toggleSidebar">
<svg viewBox="0 0 24 24" id="icon-expand">
<path d="M10 18h4v-2h-4v2zM3 13h18v-2H3v2zm0 7h12v-2H3v2zm0-14v2h18V6H3z"/>
</svg>
<svg viewBox="0 0 24 24" id="icon-collapse" style="display: none;">
<path d="M10 20h4V4h-4v16zm0-18H6v20h4V2zm8 0h-4v20h4V2z"/>
</svg>
</div>
<section id="sidebarContent">
<h2 id="openSetting" style="cursor: pointer;">Pre ChatGPT</h2>
<textarea id="questionInput" placeholder="Enter your question here..."></textarea>
<div class="button-group">
<button id="submitQuestion">Submit</button>
<button id="start">Start</button>
</div>
<ul id="questionList"></ul>
</section>
`;
document.body.appendChild(sidebar);
document.getElementById('toggleSidebar').addEventListener('click', toggleSidebar);
}
function createSettingSidebar() {
const settingSidebar = document.createElement('div');
settingSidebar.id = 'settingSidebar';
settingSidebar.style.display = 'none'; // 初始时隐藏设置侧边栏
settingSidebar.innerHTML = `
<section id="sidebarContent">
<h2 id="backToMainSidebar" style="cursor: pointer;">Pre ChatGPT</h2>
<div class="input-row">
<label for="splitCharInput">拆分符号:</label>
<input type="text" id="splitCharInput" placeholder="输入你的分隔符" />
</div>
<div class="input-row">
<label for="additionalInput">输出增强:</label>
<input type="text" id="additionalInput" placeholder="例如,详细点" />
</div>
<div class="input-row">
<label for="runMode">运行模式:</label>
<div id="runMode">
<input type="radio" id="instant" name="mode" value="instant" checked>
<label for="instant">即时</label>
<input type="radio" id="delayed" name="mode" value="delayed">
<label for="delayed">延时</label>
<input type="number" id="delayTime" placeholder="432000" disabled>
</div>
</div>
<div class="button-container1">
<button class="clear-cache-btn" onclick="clearCache()">清空缓存</button>
</div>
</section>
`;
document.body.appendChild(settingSidebar); // 将设置侧边栏添加到 DOM 中
const clearCacheBtn = document.querySelector('.clear-cache-btn');
clearCacheBtn.addEventListener('click', clearCache);
}
// 主侧边栏和设置侧边栏的创建代码
createMainSidebar();
createSettingSidebar();
document.getElementById('openSetting').addEventListener('click', function () {
// 隐藏主侧边栏
document.getElementById('sidebar').style.display = 'none';
// 显示设置侧边栏
document.getElementById('settingSidebar').style.display = '';
// 从本地存储加载设置
const splitChar = localStorage.getItem('splitChar');
const additional = localStorage.getItem('additional');
const runMode = localStorage.getItem('runMode');
const delayTime = localStorage.getItem('delayTime');
// 更新设置侧边栏中的字段
document.getElementById('splitCharInput').value = splitChar || '';
document.getElementById('additionalInput').value = additional || '';
document.getElementById('delayTime').value = delayTime || '';
if (runMode === 'instant') {
document.getElementById('instant').checked = true;
} else if (runMode === 'delayed') {
document.getElementById('delayed').checked = true;
}
});
document.getElementById('backToMainSidebar').addEventListener('click', function () {
// 获取设置侧边栏中的字段值
const splitChar = document.getElementById('splitCharInput').value;
const additional = document.getElementById('additionalInput').value;
const runMode = document.querySelector('input[name="mode"]:checked').value;
const delayTime = document.getElementById('delayTime').value;
// 保存设置到本地存储
localStorage.setItem('splitChar', splitChar);
localStorage.setItem('additional', additional);
localStorage.setItem('runMode', runMode);
localStorage.setItem('delayTime', delayTime);
// 隐藏设置侧边栏
document.getElementById('settingSidebar').style.display = 'none';
// 显示主侧边栏
document.getElementById('sidebar').style.display = '';
});
// Event listener for run mode radio button inputs
const delayedRadio = document.getElementById('delayed');
const delayTimeInput = document.getElementById('delayTime');
delayedRadio.addEventListener('change', function () {
delayTimeInput.disabled = false;
});
const instantRadio = document.getElementById('instant');
instantRadio.addEventListener('change', function () {
delayTimeInput.disabled = true;
});
function clearCache() {
var btn = document.querySelector('.clear-cache-btn');
if (typeof (Storage) !== "undefined") {
if (confirm("你确定要清空所有缓存吗?")) {
try {
localStorage.clear();
console.log("缓存已清空!");
btn.innerText = "缓存已清空";
setTimeout(function () {
btn.innerText = "清空缓存";
}, 3000); // 3秒后恢复原状
} catch (e) {
console.log("清空缓存失败,错误信息: ", e);
}
}
} else {
alert("抱歉,你的浏览器不支持 Web Storage...");
}
}
// Toggle logic
document.getElementById('toggleSidebar').addEventListener('click', function () {
var sidebar = document.getElementById('sidebar');
var iconExpand = document.getElementById('icon-expand');
var iconCollapse = document.getElementById('icon-collapse');
sidebar.classList.toggle('collapsed');
if (sidebar.classList.contains('collapsed')) {
iconCollapse.style.display = 'none';
iconExpand.style.display = '';
} else {
iconExpand.style.display = 'none';
iconCollapse.style.display = '';
}
});
(function () {
const questionList = document.getElementById('questionList');
const submitQuestionButton = document.getElementById('submitQuestion');
const questionInput = document.getElementById('questionInput');
const startButton = document.getElementById('start');
// Event listeners
submitQuestionButton.addEventListener('click', handleQuestionSubmission);
questionList.addEventListener('click', handleQuestionClick);
window.addEventListener('load', loadQuestionsFromLocalStorage);
startButton.addEventListener('click', startAskingQuestions);
// Event handlers
function handleQuestionSubmission() {
const questions = getQuestionsFromInput();
for (let question of questions) {
addQuestionToList(question);
addQuestionToLocalStorage(question);
}
clearInput();
makeQuestionListSortable();
}
function addQuestionToLocalStorage(question) {
let storedQuestions = getQuestionsFromLocalStorage();
storedQuestions.push({ text: question, answered: false });
localStorage.setItem('questions', JSON.stringify(storedQuestions));
}
function handleQuestionClick(event) {
if (event.target.classList.contains('question-text')) {
toggleQuestionTextWhiteSpace(event.target);
}
}
// Helper functions
function getQuestionsFromInput() {
// 从本地存储获取拆分符号,如果没有设置,就使用默认的符号
const splitChar = localStorage.getItem('splitChar') || '\n';
return questionInput.value.split(splitChar).filter(question => question.trim() !== '');
}
function addQuestionToList(question, answered) {
const questionDiv = createQuestionDiv(question, answered);
questionList.appendChild(questionDiv);
if (answered) {
questionDiv.classList.add('answered');
}
return questionDiv;
}
function createQuestionDiv(question, answered) {
const div = document.createElement('div');
div.className = 'question';
const questionText = document.createElement('input');
questionText.type = 'text';
questionText.className = 'question-text';
questionText.value = question;
questionText.readOnly = true;
div.appendChild(questionText);
questionText.style.border = 'none'; // Hide the textarea box border
questionText.rows = 1; // Initially show as single line
const buttonContainer = document.createElement('div');
buttonContainer.className = 'button-container';
const editButton = createButton('edit', handleEditButtonClick);
buttonContainer.appendChild(editButton);
const deleteButton = createButton('delete', handleDeleteButtonClick);
buttonContainer.appendChild(deleteButton);
const sortButton = createButton('sort');
buttonContainer.appendChild(sortButton);
div.appendChild(buttonContainer);
// Add 'answered' class if the question is answered
if (answered) {
div.classList.add('answered');
}
return div;
}
function createButton(type, clickHandler) {
const button = document.createElement('button');
button.className = `${type}-button`;
switch (type) {
case 'edit':
button.innerHTML = `<svg t="1684557647867" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3766" width="20" height="20"><path d="M862.709333 116.042667a32 32 0 1 1 45.248 45.248L455.445333 613.813333a32 32 0 1 1-45.258666-45.258666L862.709333 116.053333zM853.333333 448a32 32 0 0 1 64 0v352c0 64.8-52.533333 117.333333-117.333333 117.333333H224c-64.8 0-117.333333-52.533333-117.333333-117.333333V224c0-64.8 52.533333-117.333333 117.333333-117.333333h341.333333a32 32 0 0 1 0 64H224a53.333333 53.333333 0 0 0-53.333333 53.333333v576a53.333333 53.333333 0 0 0 53.333333 53.333333h576a53.333333 53.333333 0 0 0 53.333333-53.333333V448z" fill="#000000" p-id="3767"></path></svg>`; // SVG for edit button
break;
case 'delete':
button.innerHTML = `<svg t="1684556652392" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2423" width="20" height="20"><path d="M202.666667 256h-42.666667a32 32 0 0 1 0-64h704a32 32 0 0 1 0 64H266.666667v565.333333a53.333333 53.333333 0 0 0 53.333333 53.333334h384a53.333333 53.333333 0 0 0 53.333333-53.333334V352a32 32 0 0 1 64 0v469.333333c0 64.8-52.533333 117.333333-117.333333 117.333334H320c-64.8 0-117.333333-52.533333-117.333333-117.333334V256z m224-106.666667a32 32 0 0 1 0-64h170.666666a32 32 0 0 1 0 64H426.666667z m-32 288a32 32 0 0 1 64 0v256a32 32 0 0 1-64 0V437.333333z m170.666666 0a32 32 0 0 1 64 0v256a32 32 0 0 1-64 0V437.333333z" fill="#000000" p-id="2424"></path></svg>`; // SVG for delete button
break;
case 'sort':
button.innerHTML = `<svg t="1684417162772" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6670" width="24" height="24"><path d="M368 706.72l-64 64V576h-64v194.72l-64-64L130.72 752 272 893.28 413.28 752 368 706.72zM272 130.72L130.72 272 176 317.28l64-64V448h64V253.28l64 64L413.28 272 272 130.72zM480 192h416v64H480zM480 384h416v64H480zM480 576h416v64H480zM480 768h416v64H480z" fill="#333333" p-id="6671"></path></svg>`; // SVG for sort button
break;
}
if (clickHandler) {
button.addEventListener('click', clickHandler);
}
return button;
}
function handleEditButtonClick(event) {
const questionText = event.target.parentElement.previousSibling;
// Check if we have a 'isEditing' property and flip its value, otherwise set it to true
questionText.isEditing = !questionText.hasOwnProperty('isEditing') ? true : !questionText.isEditing;
if (questionText.isEditing) {
questionText.readOnly = false;
questionText.style.border = '1px solid'; // Show border when editing
questionText.rows = 'auto'; // Expand the textarea box to show all text
questionText.focus();
// Move the cursor to the end of the text
questionText.selectionStart = questionText.selectionEnd = questionText.value.length;
} else {
questionText.readOnly = true;
questionText.style.border = 'none'; // Hide the textarea box border again after editing
questionText.rows = 1; // Collapse the textarea box back to single line
// Update the question text in localStorage
let storedQuestions = localStorage.getItem('questions');
storedQuestions = storedQuestions ? JSON.parse(storedQuestions) : [];
let questionToUpdate = storedQuestions.find(q => q.text === questionText.value);
if (questionToUpdate) {
questionToUpdate.text = questionText.value;
localStorage.setItem('questions', JSON.stringify(storedQuestions));
}
}
}
function handleDeleteButtonClick(event) {
// Use the closest method to get the question div
const questionDiv = event.target.closest('.question');
const questionText = questionDiv.querySelector('input.question-text');
// Remove the question from the DOM
questionDiv.remove();
// Remove the question from localStorage
let storedQuestions = localStorage.getItem('questions');
storedQuestions = storedQuestions ? JSON.parse(storedQuestions) : [];
storedQuestions = storedQuestions.filter(q => q.text !== questionText.value);
localStorage.setItem('questions', JSON.stringify(storedQuestions));
}
function clearInput() {
questionInput.value = '';
}
function makeQuestionListSortable() {
new Sortable(questionList, {
handle: '.sort-button',
animation: 150
});
}
function toggleQuestionTextWhiteSpace(questionText) {
questionText.style.whiteSpace = questionText.style.whiteSpace === 'nowrap' ? 'normal' : 'nowrap';
}
function loadQuestionsFromLocalStorage() {
const storedQuestions = getQuestionsFromLocalStorage();
for (let question of storedQuestions) {
let questionDiv = addQuestionToList(question.text, question.answered);
if (question.answered) {
questionDiv.classList.add('answered');
}
}
}
function getQuestionsFromLocalStorage() {
let storedQuestions = localStorage.getItem('questions');
return storedQuestions ? JSON.parse(storedQuestions) : [];
}
async function startAskingQuestions() {
const questions = Array.from(document.getElementsByClassName('question'));
const runMode = localStorage.getItem('runMode');
const delayTime = parseInt(localStorage.getItem('delayTime') || '300');
for (let i = 0; i < questions.length; i++) {
const questionDiv = questions[i];
const questionInput = questionDiv.querySelector('input.question-text');
if (!questionDiv.classList.contains('answered')) {
if (runMode === 'instant') {
await askQuestionInstant(questionInput.value);
} else if (runMode === 'delayed') {
await delay(delayTime);
await askQuestionDelayed(questionInput.value);
}
questionDiv.classList.add('answered');
updateQuestionInLocalStorage(questionInput.value, true);
}
}
}
async function askQuestionInstant(question) {
return new Promise((resolve, reject) => {
const additional = localStorage.getItem('additional') || '';
const questionToSend = `${question} ${additional}`.trim();
const inputBox = document.querySelector('textarea');
inputBox.value = questionToSend;
const event = new Event('input', { bubbles: true });
inputBox.dispatchEvent(event);
const sendButton = inputBox.nextElementSibling;
setTimeout(() => {
sendButton.click();
}, 500);
const observer = new MutationObserver((mutations, observer) => {
for (let mutation of mutations) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
for (let node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE && node.textContent.includes('Regenerate response')) {
observer.disconnect();
resolve();
return;
}
}
}
}
});
const observerConfig = { childList: true, subtree: true };
observer.observe(document.body, observerConfig);
});
}
function askQuestionDelayed(question) {
return new Promise((resolve, reject) => {
if (!question) {
reject('No question provided');
return;
}
const additional = localStorage.getItem('additional') || '';
const delayTime = parseInt(localStorage.getItem('delayTime'), 10);
if (isNaN(delayTime)) {
reject('Invalid delayTime');
return;
}
const questionToSend = `${question} ${additional}`.trim();
const inputBox = document.querySelector('textarea');
if (!inputBox) {
reject('Input box not found');
return;
}
inputBox.value = questionToSend;
const event = new Event('input', { bubbles: true });
inputBox.dispatchEvent(event);
const sendButton = inputBox.nextElementSibling;
if (!sendButton) {
reject('Send button not found');
return;
}
setTimeout(() => {
sendButton.click();
resolve();
}, delayTime);
});
}
function updateQuestionInLocalStorage(questionText, answered) {
let storedQuestions = getQuestionsFromLocalStorage();
let questionToUpdate = storedQuestions.find(q => q.text === questionText);
if (questionToUpdate) {
questionToUpdate.answered = answered;
localStorage.setItem('questions', JSON.stringify(storedQuestions));
}
}
function delay(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms);
});
}
})();
})();