Pre ChatGPT

Handle multiple questions for ChatGPT

当前为 2023-06-02 提交的版本,查看 最新版本

// ==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);
      });
    }
  })();
})();