// ==UserScript==
// @name Chatgpt 角色扮演助手/Chatgpt Role Play Helper
// @namespace http://tampermonkey.net/
// @version 1.3
// @description 在屏幕中央弹出一个可拖动位置的悬浮窗,悬浮窗内有三个文本框并且可以编辑,以及一个按钮,点击按钮后将这三个文本框的内容合并,并将合并后的文本输入到页面中符合特定CSS类别的文本框中,最后触发符合特定CSS类别的提交按钮以提交表单。
// @author Chatgpt (most)and 环白
// @match https://chat.openai.com/*
// @grant GM_addStyle
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
// 设置字体为黑色
const elements = document.getElementsByTagName('*');
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const styles = getComputedStyle(element);
const bgColor = styles.backgroundColor;
if (bgColor === 'transparent' || bgColor === 'rgba(0, 0, 0, 0)') {
element.style.color = 'black';
}
}
// 创建悬浮按钮
const floatingButton = document.createElement('button');
floatingButton.id = 'floating-button';
floatingButton.innerText = '展开/Unfold';
floatingButton.title = '角色扮演助手/Role Play Helper';
floatingButton.style.position = 'fixed';
floatingButton.style.right = '50px';
floatingButton.style.bottom = '50px';
floatingButton.style.backgroundColor = 'lightblue';
floatingButton.style.padding = '10px';
floatingButton.style.borderRadius = '5px';
floatingButton.style.border = 'none';
floatingButton.style.cursor = 'pointer';
floatingButton.style.fontWeight = 'bold';
document.body.appendChild(floatingButton);
// 创建悬浮窗和文本框
const floatingDiv = document.createElement('div');
floatingDiv.id = 'floating-div';
floatingDiv.style.position = 'fixed';
floatingDiv.style.top = 'calc(40% + 50px)';
floatingDiv.style.height = '600px';
floatingDiv.style.left = 'calc(100% - 350px)';
floatingDiv.style.transform = 'translate(-50%, -50%)';
floatingDiv.style.zIndex = '9999';
floatingDiv.style.backgroundColor = 'white';
floatingDiv.style.border = '1px solid black';
floatingDiv.style.padding = '10px';
floatingDiv.style.borderRadius = '5px';
floatingDiv.style.display = 'none';
floatingDiv.style.width = '400px';
document.body.appendChild(floatingDiv);
const input1 = document.createElement('textarea');
input1.id = 'input1';
input1.style.width = '100%';
input1.style.height = '100px';
input1.style.marginBottom = '0px';
floatingDiv.appendChild(input1);
const spaceDiv = document.createElement('div');
spaceDiv.style.height = '60px'; // 设置空白div的高度
floatingDiv.insertBefore(spaceDiv, input1); // 将空白div插入到input1前面
const input2 = document.createElement('textarea');
input2.id = 'input2';
input2.style.width = '100%';
input2.style.height = '100px';
input2.style.marginBottom = '0px';
floatingDiv.appendChild(input2);
const input3 = document.createElement('textarea');
input3.id = 'input3';
input3.style.width = '100%';
input3.style.height = '100px';
input3.style.marginBottom = '0px';
floatingDiv.appendChild(input3);
// 创建注释标签
const label1 = document.createElement('label');
label1.innerHTML = '以下内容包括辅助文本会被合并输入:';
floatingDiv.insertBefore(label1, input1);
label1.style.fontFamily = '楷体';
label1.style.fontSize = '14px';
label1.style.fontWeight = 'bold';
const label2 = document.createElement('label');
label2.innerHTML = '回顾区(回顾区存放着ChatGPT你上一次交互过程中的响应){ ';
floatingDiv.insertBefore(label2, input2);
label2.style.fontFamily = '楷体';
label2.style.fontSize = '14px';
label2.style.fontWeight = 'bold';
const label3 = document.createElement('label');
label3.innerHTML = '}
交互区(我所扮演的角色的行为、对话和心理,格式【{角色名:(行为、心理)对话】){';
floatingDiv.insertBefore(label3, input3);
label3.style.fontFamily = '楷体';
label3.style.fontSize = '14px';
label3.style.fontWeight = 'bold';
const label4 = document.createElement('div');
label4.innerHTML = '
}
'; floatingDiv.insertBefore(label4, input3.nextSibling); label4.style.fontFamily = '楷体'; label4.style.fontSize = '14px'; label4.style.fontWeight = 'bold'; label1.classList.add('label'); label2.classList.add('label'); label3.classList.add('label'); label4.classList.add('label'); // 创建按钮 const toggleButton = document.createElement('button'); toggleButton.innerHTML = ''; toggleButton.title = '开启/关闭辅助文本(关闭则不合并辅助文本)'; floatingDiv.appendChild(toggleButton); // 添加点击事件 toggleButton.addEventListener('click', function() { const labels = document.querySelectorAll('.label'); labels.forEach((label, index) => { if (index === 0) { label.style.display = 'block'; // 让第一个label(即label1)始终显示 } else if (label.style.display === 'none') { label.style.display = 'block'; } else { label.style.display = 'none'; } }); }); // 添加样式 const style = document.createElement('style'); style.innerHTML = ` .circle { width: 20px; height: 20px; border: 2px solid #000; border-radius: 50%; margin: auto; position: relative; } .circle::before, .circle::after { content: ''; display: block; position: absolute; background-color: #000; } .circle::before { width: 10px; height: 2px; top: 50%; left: 50%; transform: translate(-50%, -50%); } .circle::after { width: 2px; height: 10px; top: 50%; left: 50%; transform: translate(-50%, -50%); } `; document.head.appendChild(style); // 创建加载存档按钮和保存存档按钮 const archiveButtonLeft = document.createElement('button'); archiveButtonLeft.id = 'archive-button-left'; archiveButtonLeft.innerText = '加载/Load'; archiveButtonLeft.title = '点击选择想要加载的存档'; archiveButtonLeft.style.padding = '10px'; archiveButtonLeft.style.borderRadius = '5px'; archiveButtonLeft.style.backgroundColor = 'lightblue'; archiveButtonLeft.style.border = '1px solid black'; archiveButtonLeft.style.cursor = 'pointer'; archiveButtonLeft.style.float = 'left'; floatingDiv.insertBefore(archiveButtonLeft, spaceDiv); const archiveButtonRight = document.createElement('button'); archiveButtonRight.id = 'archive-button-right'; archiveButtonRight.innerText = '保存/Save'; archiveButtonRight.title = '点击选择想要保存的存档'; archiveButtonRight.style.padding = '10px'; archiveButtonRight.style.borderRadius = '5px'; archiveButtonRight.style.backgroundColor = 'lightblue'; archiveButtonRight.style.border = '1px solid black'; archiveButtonRight.style.cursor = 'pointer'; archiveButtonRight.style.float = 'right'; floatingDiv.insertBefore(archiveButtonRight, spaceDiv); // 修改加载存档按钮样式 archiveButtonLeft.style.border = 'none'; archiveButtonLeft.style.fontFamily = 'cursive'; archiveButtonLeft.style.fontWeight = 'bold'; archiveButtonLeft.style.color = 'black'; // 修改保存按钮样式 archiveButtonRight.style.border = 'none'; archiveButtonRight.style.fontFamily = 'cursive'; archiveButtonRight.style.fontWeight = 'bold'; archiveButtonRight.style.color = 'black'; // 创建加载存档弹窗 const archivePopup = document.createElement('div'); archivePopup.id = 'archive-popup-left'; archivePopup.style.position = 'fixed'; archivePopup.style.top = '50%'; archivePopup.style.left = '50%'; archivePopup.style.transform = 'translate(-50%, -50%)'; archivePopup.style.zIndex = '9999'; archivePopup.style.backgroundColor = 'white'; archivePopup.style.border = '1px solid black'; archivePopup.style.padding = '10px'; archivePopup.style.borderRadius = '5px'; archivePopup.style.display = 'none'; archivePopup.style.width = '200px'; document.body.appendChild(archivePopup); // 创建加载存档弹窗的关闭按钮 const closeArchiveButton = document.createElement('button'); closeArchiveButton.id = 'close-archive-button'; closeArchiveButton.innerHTML = '❌'; closeArchiveButton.title = '关闭'; closeArchiveButton.style.padding = '10px'; closeArchiveButton.style.borderRadius = '5px'; closeArchiveButton.style.backgroundColor = 'lightgray'; closeArchiveButton.style.border = '1px solid black'; closeArchiveButton.style.cursor = 'pointer'; closeArchiveButton.style.float = 'right'; closeArchiveButton.style.border = 'none'; closeArchiveButton.style.backgroundColor = 'transparent'; archivePopup.appendChild(closeArchiveButton); // 点击加载存档弹窗的关闭按钮关闭弹窗 closeArchiveButton.addEventListener('click', function() { archivePopup.style.display = 'none'; }); // 添加存档1、存档2、存档3按钮到加载存档弹窗 const archiveButton1 = document.createElement('button'); archiveButton1.id = 'archive-1'; archiveButton1.innerText = '存档1'; archiveButton1.title = '存档1'; archiveButton1.style.padding = '10px'; archiveButton1.style.borderRadius = '5px'; archiveButton1.style.backgroundColor = 'lightblue'; archiveButton1.style.border = 'none'; archiveButton1.style.cursor = 'pointer'; archiveButton1.style.marginBottom = '10px'; archiveButton1.style.fontWeight = 'bold'; archivePopup.appendChild(archiveButton1); const archiveButton2 = document.createElement('button'); archiveButton2.id = 'archive-2'; archiveButton2.innerText = '存档2'; archiveButton2.title = '存档2'; archiveButton2.style.padding = '10px'; archiveButton2.style.borderRadius = '5px'; archiveButton2.style.backgroundColor = 'lightblue'; archiveButton2.style.border = 'none'; archiveButton2.style.cursor = 'pointer'; archiveButton2.style.marginBottom = '10px'; archiveButton2.style.fontWeight = 'bold'; archivePopup.appendChild(archiveButton2); const archiveButton3 = document.createElement('button'); archiveButton3.id = 'archive-3'; archiveButton3.innerText = '存档3'; archiveButton3.title = '存档3'; archiveButton3.style.padding = '10px'; archiveButton3.style.borderRadius = '5px'; archiveButton3.style.backgroundColor = 'lightblue'; archiveButton3.style.border = 'none'; archiveButton3.style.cursor = 'pointer'; archiveButton3.style.marginBottom = '10px'; archiveButton3.style.fontWeight = 'bold'; archivePopup.appendChild(archiveButton3); // 创建保存弹窗 const savePopup = document.createElement('div'); savePopup.id = 'save-popup'; savePopup.style.position = 'fixed'; savePopup.style.top = '50%'; savePopup.style.left = '50%'; savePopup.style.transform = 'translate(-50%, -50%)'; savePopup.style.zIndex = '9999'; savePopup.style.backgroundColor = 'white'; savePopup.style.border = '1px solid black'; savePopup.style.padding = '10px'; savePopup.style.borderRadius = '5px'; savePopup.style.display = 'none'; savePopup.style.width = '300px'; document.body.appendChild(savePopup); // 创建保存弹窗的关闭按钮 const closeSaveButton = document.createElement('button'); closeSaveButton.id = 'close-save-button'; closeSaveButton.innerHTML = '❌'; closeArchiveButton.title = '关闭'; closeSaveButton.style.padding = '10px'; closeSaveButton.style.borderRadius = '5px'; closeSaveButton.style.backgroundColor = 'transparent'; closeSaveButton.style.border = 'none'; closeSaveButton.style.cursor = 'pointer'; closeSaveButton.style.float = 'right'; savePopup.appendChild(closeSaveButton); // 点击保存弹窗的关闭按钮关闭弹窗 closeSaveButton.addEventListener('click', function() { savePopup.style.display = 'none'; }); // 添加导入存档按钮到保存弹窗 const importButton1 = document.createElement('button'); importButton1.id = 'import-button1'; importButton1.innerText = '存入存档1'; importButton1.title = '存入存档1'; importButton1.style.padding = '10px'; importButton1.style.borderRadius = '5px'; importButton1.style.backgroundColor = 'lightblue'; importButton1.style.border = 'none'; importButton1.style.cursor = 'pointer'; importButton1.style.fontWeight = 'bold'; importButton1.style.float = 'left'; savePopup.appendChild(importButton1); const importButton2 = document.createElement('button'); importButton2.id = 'import-button2'; importButton2.innerText = '存入存档2'; importButton2.title = '存入存档2'; importButton2.style.padding = '10px'; importButton2.style.borderRadius = '5px'; importButton2.style.backgroundColor = 'lightblue'; importButton2.style.border = 'none'; importButton2.style.cursor = 'pointer'; importButton2.style.fontWeight = 'bold'; importButton2.style.float = 'left'; savePopup.appendChild(importButton2); const importButton3 = document.createElement('button'); importButton3.id = 'import-button3'; importButton3.innerText = '存入存档3'; importButton3.title = '存入存档3'; importButton3.style.padding = '10px'; importButton3.style.borderRadius = '5px'; importButton3.style.backgroundColor = 'lightblue'; importButton3.style.border = 'none'; importButton3.style.cursor = 'pointer'; importButton3.style.fontWeight = 'bold'; importButton3.style.float = 'left'; savePopup.appendChild(importButton3); const MAX_CHUNK_COUNT = 100; // 存储存档数据的对象 const archiveData = { archive1: { text1: '', text2: '', text3: '' }, archive2: { text1: '', text2: '', text3: '' }, archive3: { text1: '', text2: '', text3: '' } }; // 点击存储按钮将悬浮窗的三个文本框内的文本存储到“存档1”按钮中 importButton1.addEventListener('click', function() { archiveData.archive1.text1 = input1.value; archiveData.archive1.text2 = input2.value; archiveData.archive1.text3 = input3.value; localStorage.setItem('archiveData', JSON.stringify(archiveData)); input1.value = ''; input2.value = ''; input3.value = ''; savePopup.style.display = 'none'; }); // 点击存储按钮将悬浮窗的三个文本框内的文本存储到“存档2”按钮中 importButton2.addEventListener('click', function() { archiveData.archive2.text1 = input1.value; archiveData.archive2.text2 = input2.value; archiveData.archive2.text3 = input3.value; localStorage.setItem('archiveData', JSON.stringify(archiveData)); input1.value = ''; input2.value = ''; input3.value = ''; savePopup.style.display = 'none'; }); // 点击存储按钮将悬浮窗的三个文本框内的文本存储到“存档3”按钮中 importButton3.addEventListener('click', function() { archiveData.archive3.text1 = input1.value; archiveData.archive3.text2 = input2.value; archiveData.archive3.text3 = input3.value; localStorage.setItem('archiveData', JSON.stringify(archiveData)); input1.value = ''; input2.value = ''; input3.value = ''; savePopup.style.display = 'none'; }); // 点击存档1按钮将“存档1”中的文本替换到悬浮窗的三个文本框内 archiveButton1.addEventListener('click', function() { const archiveData = JSON.parse(localStorage.getItem('archiveData')); const texts = [ archiveData.archive1.text1, archiveData.archive1.text2, archiveData.archive1.text3 ]; input1.value = texts[0]; input2.value = texts[1]; input3.value = texts[2]; archivePopup.style.display = 'none'; }); // 点击存档2按钮将“存档2”中的文本替换到悬浮窗的三个文本框内 archiveButton2.addEventListener('click', function() { const archiveData = JSON.parse(localStorage.getItem('archiveData')); const texts = [ archiveData.archive2.text1, archiveData.archive2.text2, archiveData.archive2.text3 ]; input1.value = texts[0]; input2.value = texts[1]; input3.value = texts[2]; archivePopup.style.display = 'none'; }); // 点击存档3按钮将“存档3”中的文本替换到悬浮窗的三个文本框内 archiveButton3.addEventListener('click', function() { const archiveData = JSON.parse(localStorage.getItem('archiveData')); const texts = [ archiveData.archive3.text1, archiveData.archive3.text2, archiveData.archive3.text3 ]; input1.value = texts[0]; input2.value = texts[1]; input3.value = texts[2]; archivePopup.style.display = 'none'; }); let archivePopupVisible = false; archiveButtonLeft.addEventListener('click', function() { // 隐藏弹窗并清除之前的位置信息 archivePopup.style.display = 'none'; archivePopup.style.left = ''; archivePopup.style.top = ''; archivePopupVisible = false; if (!archivePopupVisible) { // 获取加载存档按钮的位置和尺寸 const buttonRect = archiveButtonLeft.getBoundingClientRect(); const buttonWidth = buttonRect.width; const buttonHeight = buttonRect.height; // 计算弹窗的位置 const popupLeft = buttonRect.left + buttonWidth + savePopup.offsetWidth; const popupTop = buttonRect.top - buttonHeight/2 - archivePopup.offsetHeight/2; // 设置弹窗的位置 archivePopup.style.left = popupLeft + 'px'; archivePopup.style.top = popupTop + 'px'; // 显示弹窗 archivePopup.style.display = 'block'; archivePopupVisible = true; } }); // 点击加载存档按钮弹出加载存档弹窗 archiveButtonLeft.addEventListener('click', function() { // 获取加载存档按钮的位置和尺寸 const buttonRect = archiveButtonLeft.getBoundingClientRect(); const buttonWidth = buttonRect.width; const buttonHeight = buttonRect.height; // 计算弹窗的位置 const popupLeft = buttonRect.left + buttonWidth - archivePopup.offsetWidth / 3; const popupTop = buttonRect.top + archivePopup.offsetHeight/2.4; // 设置弹窗的位置 archivePopup.style.left = popupLeft + 'px'; archivePopup.style.top = popupTop + 'px'; // 显示弹窗 archivePopup.style.display = 'block'; }); // 点击加载存档弹窗的关闭按钮或其他地方关闭弹窗 closeArchiveButton.addEventListener('click', function() { archivePopup.style.display = 'none'; archivePopupVisible = false; }); archivePopup.addEventListener('click', function(event) { if (event.target === archivePopup) { archivePopup.style.display = 'none'; archivePopupVisible = false; } }); // 点击保存按钮弹出保存弹窗 let savePopupVisible = false; archiveButtonRight.addEventListener('click', function() { // 获取保存按钮的位置和尺寸 const savebuttonRect = archiveButtonRight.getBoundingClientRect(); const savebuttonWidth = savebuttonRect.width; const savebuttonHeight = savebuttonRect.height; // 计算弹窗的位置 const savepopupLeft = savebuttonRect.left + savebuttonWidth + savePopup.offsetWidth; const savepopupTop = savebuttonRect.top + savebuttonHeight + savePopup.offsetHeight; // 计算弹窗的最大左边距和上边距,防止弹窗超出浏览器窗口 const maxLeft = window.innerWidth - savePopup.offsetWidth; const maxTop = window.innerHeight - savePopup.offsetHeight; // 设置弹窗的位置 savePopup.style.left = Math.min(savepopupLeft, maxLeft) - savePopup.offsetWidth + 'px'; savePopup.style.top = Math.min(savepopupTop, maxTop) - savePopup.offsetHeight + 'px'; // 显示弹窗 savePopup.style.display = 'block'; }); // 点击保存弹窗的关闭按钮或其他地方关闭弹窗 closeSaveButton.addEventListener('click', function() { savePopup.style.display = 'none'; savePopupVisible = false; }); savePopup.addEventListener('click', function(event) { if (event.target === savePopup) { savePopup.style.display = 'none'; savePopupVisible = false; } }); // 创建发送按钮 const button = document.createElement('button'); button.id = 'merge-button'; button.innerText = '发送/Send'; button.title = '合并以上文本进行发送'; button.style.padding = '10px'; button.style.borderRadius = '5px'; button.style.backgroundColor = 'lightblue'; button.style.border = '1px solid black'; button.style.cursor = 'pointer'; button.style.float = 'right'; // 修改发送按钮样式 button.style.border = 'none'; button.style.fontFamily = 'cursive'; button.style.fontWeight = 'bold'; button.style.color = 'black'; floatingDiv.appendChild(button); // 在悬浮窗左下角添加文本 const helperText = document.createElement('div'); helperText.innerText = 'Role Play Helper'; helperText.title = '这是一个帮助文本,它提供了一些有用的信息来帮助您使用本脚本。\n帮助:将你的鼠标放在每一个你能看到的图标上,仔细查看这些图标的注解。' helperText.style.position = 'absolute'; helperText.style.bottom = '10px'; helperText.style.left = '10px'; helperText.style.fontFamily = 'cursive'; helperText.style.fontWeight = 'bold'; helperText.style.fontStyle = 'italic'; helperText.style.fontSize = '16px'; floatingDiv.appendChild(helperText); // 设置悬浮窗可拖动 let isDragging = false; let offsetX = 0; let offsetY = 0; const textInputs = [input1, input2, input3]; floatingDiv.addEventListener('mousedown', function(event) { // 检查事件目标是否是文本框 if (textInputs.includes(event.target)) { return; } isDragging = true; offsetX = event.clientX - floatingDiv.offsetLeft; offsetY = event.clientY - floatingDiv.offsetTop; }); document.addEventListener('mousemove', function(event) { if (isDragging) { floatingDiv.style.left = (event.clientX - offsetX) + 'px'; floatingDiv.style.top = (event.clientY - offsetY) + 'px'; } }); document.addEventListener('mouseup', function() { isDragging = false; }); function generateOutputArray(selector, num = 0) { const matchedDivs = document.querySelectorAll(selector); const results = []; let startIdx = 0; if (num > 0) { startIdx = Math.max(matchedDivs.length - num, 0); } matchedDivs.forEach((div, idx) => { if (idx >= startIdx) { const hasFlexBetweenChild = div.querySelector('div.flex.justify-between') !== null; const flexBetweenDiv = div.querySelector('div.flex.justify-between'); const hasChild = flexBetweenDiv && flexBetweenDiv.children.length > 0; const text = div.textContent.trim(); let role = hasChild ? "assistant" : "user"; results.push({ role, content: text }); } }); return results; } //生成指定限制数量和字数长度的会话数组 function generateOutputArrayWithMaxLength(selector, num = 0, maxLength = Infinity) { const outputArray = generateOutputArray(selector, num); let totalLength = 0; let resultArray = []; for (let i = outputArray.length - 1; i >= 0; i--) { const { role, content } = outputArray[i]; totalLength += content.length; if (totalLength > maxLength || resultArray.length >= num) { break; } resultArray.unshift({ role, content }); } return resultArray; } //格式化会话数组为导出文本 function formatOutputArray(outputArray) { return outputArray .map(({ role, content }) => `${role}: ${content}`) .join('\r\n\r\n----------------\r\n\r\n'); } //创建一个下载文本 function downloadTextFile(text, filename) { const blob = new Blob([text], { type: "text/plain;charset=utf-8" }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = `${filename}.txt`; a.textContent = `Download ${filename}`; document.body.appendChild(a); a.click(); document.body.removeChild(a); } //获取最新的助手消息 function getLatestAssistantMessage(selector) { const matchedDivs = document.querySelectorAll(selector); for (let i = matchedDivs.length - 1; i >= 0; i--) { const div = matchedDivs[i]; const flexBetweenDiv = div.querySelector('div.flex.justify-between'); if (flexBetweenDiv && flexBetweenDiv.children.length > 0) { const text = div.textContent.trim(); return text; } } return null; } // 创建按钮元素 const button2 = document.createElement('button'); button2.innerHTML = ''; button2.title = '复制GPT最新回复到回顾区'; floatingDiv.appendChild(button2); // 修改按钮样式 button2.style.position = 'absolute'; button2.style.top = '90%'; button2.style.left = '50%'; button2.style.transform = 'translate(-50%, 10px)'; button2.style.border = 'none'; button2.style.background = 'transparent'; button2.style.cursor = 'pointer'; ; // 给按钮添加点击事件监听器 button2.addEventListener('click', function() { const latestAssistantMessage = getLatestAssistantMessage('div[class*="w-[calc(100%"]'); if (latestAssistantMessage) { input2.value = latestAssistantMessage; } }); button.addEventListener('click', function() { const inputText1 = input1.value.trim(); const inputText2 = input2.value.trim(); const inputText3 = input3.value.trim(); let mergedTextoutput = ''; if (inputText1 !== '') { mergedTextoutput += inputText1 + '\n'; } if (label2.style.display !== 'none' && inputText2 !== '') { mergedTextoutput += label2.textContent.trim() + '\n'; } if (inputText2 !== '') { mergedTextoutput += inputText2 + '\n'; } if (label3.style.display !== 'none' && inputText3 !== '') { mergedTextoutput += label3.innerHTML.trim().replace(/