Greasy Fork

来自缓存

Greasy Fork is available in English.

ChatGPT轻小说分段翻译

上传长文本TXT, 并分段翻译成简体中文。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         ChatGPT轻小说分段翻译
// @namespace    http://tampermonkey.net/
// @version      0.15
// @description  上传长文本TXT, 并分段翻译成简体中文。
// @author       root
// @match        https://chat.openai.com/*
// @match        https://chatgpt.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=openai.com
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // Initialize default values
    let num = 15;
    let shouldStop = false;
    let prefixText = "";

    // CSS styles for the elements
    const styles = {
        button:
        "background-color: #FAE69E; font-weight: bold; width:100px; height:30px; color: #927201; padding: 5px; border: none; border-radius: 5px; margin: 5px; font-size: 14px; cursor: pointer; transition: all 0.3s ease;",
        greenButton:
        "background-color: #19C37D; font-weight: bold; width:100px; height:30px; color: white; padding: 5px; border: none; border-radius: 5px; margin: 5px; font-size: 14px; cursor: pointer; transition: all 0.3s ease;",
        stopButton:
        "background-color: #dc3545; font-weight: bold; width:100px; height:30px; color: white; padding: 5px; border: none; border-radius: 5px; margin: 5px; font-size: 14px; cursor: pointer; transition: all 0.3s ease;",
        aboutButton:
        "background-color: #FAE69E; font-weight: bold; width:100px; height:30px; color: #927201; padding: 5px; border: none; border-radius: 5px; margin: 5px; font-size: 14px; cursor: pointer; transition: all 0.3s ease;",
        progress:
        "width: 70%; height: 15px; background-color: #d9d9e3; border-radius: 15px; margin-top: 10px; overflow: hidden; margin: 0 auto; box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);",
        progressBar:
        "height: 100%; background: linear-gradient(90deg, #19C37D, #1B98E0); width: 0%; transition: width 0.5s ease-in-out; border-radius: 15px;",
        input:
        "margin: 5px; width: 120px; height: 30px; padding: 5px; font-size: 14px; border: 1px solid #ccc; border-radius: 5px;",
    };

    // Function to create an input
    const createInput = (placeholderText) => {
        const input = document.createElement("input");
        input.type = "number";
        input.placeholder = placeholderText;
        input.style = styles.input;
        return input;
    };

    // Function to create a file input
    const createFileInput = () => {
        const input = document.createElement("input");
        input.type = "file";
        input.accept = ".txt,.js,.py,.html,.css,.json,.csv";
        input.style = styles.input;
        return input;
    };

    // Function to create a button
    const createButton = (text, style) => {
        const button = document.createElement("button");
        button.style = style;
        button.textContent = text;
        // Add hover style
        button.onmouseover = function () {
            this.style.border = "2px solid rgba(0, 0, 0, 0.5)"; // shallow black border on hover
        };
        button.onmouseout = function () {
            this.style.border = "none"; // remove border when mouse leaves
        };
        return button;
    };

    // Function to create a button wrapper (div)
    const createButtonWrapper = (
        submitButton,
        aboutButton,
        input1,
        input2,
        prefixButton
    ) => {
        const div = document.createElement("div");
        div.classList.add("buttonWrapper");
        div.appendChild(input1);
        div.appendChild(input2);
        div.appendChild(prefixButton);
        div.appendChild(submitButton);
        // div.appendChild(aboutButton);
        div.setAttribute("data-inserted", "true");
        return div;
    };

    // Function to create a progress element
    const createProgress = () => {
        const progress = document.createElement("div");
        progress.style = styles.progress;
        const progressBar = document.createElement("div");
        progressBar.style = styles.progressBar;
        const progressAnimation = document.createElement("style");
        progressAnimation.type = "text/css";
        progressAnimation.innerHTML = `
    @keyframes gradient {
      0% { background-position: 0% 50%; }
      50% { background-position: 100% 50%; }
      100% { background-position: 0% 50%; }
    }
    .animated-gradient {
      background-size: 200% 200%;
      animation: gradient 5s ease infinite;
    }
  `;
    document.head.appendChild(progressAnimation);
    progressBar.classList.add("animated-gradient");
    progress.appendChild(progressBar);
    return { progress, progressBar };
};

    function sleep(ms) {
        return new Promise((resolve) => setTimeout(resolve, ms));
    }

    // Async function to submit conversation
    async function submitConversation(text, part, filename, delay) {
        const textarea = document.querySelector("textarea[tabindex='0']");
        const inputEvent = new Event("input", {
            bubbles: true,
            cancelable: true,
        });
        const enterKeyEvent = new KeyboardEvent("keydown", {
            bubbles: true,
            cancelable: true,
            keyCode: 13,
        });
        textarea.value = `Part ${part} of ${filename}: \n\n ${prefixText} ${text}`;
        textarea.dispatchEvent(inputEvent);
        await sleep(delay);
        textarea.dispatchEvent(enterKeyEvent);
    }

    // Function to check if ChatGPT is ready
    async function isChatGptReady() {
        let chatgptReady = false;
        while (!chatgptReady) {
            await new Promise((resolve) => setTimeout(resolve, 1000));
            //chatgptReady = !document.querySelector(".text-2xl > span:not(.invisible)");
            //const newElement = document.querySelector('[data-testid="send-button"]');
            const newElement = document.querySelector('button.mb-1.me-1');
            if (newElement) {
                const isDisabled = newElement.hasAttribute("disabled");
                if (isDisabled) {
                    chatgptReady = true;
                }
            }
        }
        return chatgptReady;
    }

    // Function to split text into sentences
    function splitIntoSentences(text) {
        // Use regular expression to split text by sentence
        return text.match(/[^。!?”.!?]+[。!?”.!?]+/g);
    }

    // Function to group sentences
    function groupSentences(sentences, groupSize) {
        const groups = [];
        for (let i = 0; i < sentences.length; i += groupSize) {
            groups.push(sentences.slice(i, i + groupSize).join(""));
        }
        return groups;
    }

    const initPlugin = () => {
        // Check if the element exists and insert the elements into the DOM
        const checkExist = setInterval(function () {

            const parentElement = document.querySelector(
                ".px-2.py-2"
            );
            if (
                parentElement !== null &&
                !parentElement.getAttribute("data-inserted")
            ) {
                console.log("Element exists!");
                // 添加一个标记,表示已经插入了插件
                parentElement.setAttribute("data-inserted", "true");
                // Create elements
                prefixText = "将以下内容翻译成简体中文:";
                const submitButton = createButton("上传文件", styles.button);
                const aboutButton = createButton("关于", styles.aboutButton);
                const numInput = createInput("单次句子数量");
                const delayInput = createInput("延迟时间(秒)");
                const prefixButton = createButton("前置提示词", styles.button);
                const buttonWrapper = createButtonWrapper(
                    submitButton,
                    aboutButton,
                    numInput,
                    delayInput,
                    prefixButton
                );

                const { progress, progressBar } = createProgress();
                parentElement.insertBefore(buttonWrapper, parentElement.firstChild);
                parentElement.insertBefore(progress, parentElement.firstChild);
                clearInterval(checkExist);

                // Handle prefix button click
                prefixButton.addEventListener("click", () => {
                    const prefixInput = createFileInput();

                    const fileChangeListener = async () => {
                        if (!prefixInput.files.length) {
                            // add this check
                            prefixInput.removeEventListener("change", fileChangeListener);
                            return;
                        }

                        const file = prefixInput.files[0];
                        const reader = new FileReader();
                        reader.readAsText(file);
                        reader.onload = () => {
                            prefixText = reader.result;
                            prefixButton.style = styles.greenButton;
                        };
                    };

                    prefixInput.addEventListener("change", fileChangeListener);

                    prefixInput.click();
                });

                submitButton.addEventListener("click", async () => {
                    // If a file is uploading, stop it
                    if (submitButton.textContent === "停止") {
                        shouldStop = true;
                        progressBar.style.width = "0%";
                        submitButton.textContent = "上传文件"; // change the button text back to 'Upload file'
                        submitButton.style = styles.button; // change the button color back to blue
                        return;
                    }

                    // Get values from inputs and update variables
                    num = numInput.value ? parseInt(numInput.value) : num;
                    const delay = delayInput.value
                    ? parseInt(delayInput.value) * 1000
                    : 1000;
                    const input = createFileInput();

                    const fileChangeListener = async () => {
                        if (!input.files.length) {
                            // add this check
                            input.removeEventListener("change", fileChangeListener);
                            return;
                        }

                        const file = input.files[0];
                        const reader = new FileReader();
                        reader.readAsText(file);
                        reader.onload = async () => {
                            // Change the button to a stop button after file read is completed
                            submitButton.textContent = "停止"; // change the button text to 'Stop'
                            submitButton.style = styles.stopButton; // change the button color to red

                            const sentences = splitIntoSentences(reader.result);
                            const chunks = groupSentences(sentences, num);
                            for (let i = 0; i < chunks.length; i++) {
                                if (shouldStop) {
                                    shouldStop = false;
                                    break;
                                }
                                if (!shouldStop) {
                                    await submitConversation(chunks[i], i + 1, file.name, delay);
                                }
                                progressBar.style.width = `${((i + 1) / chunks.length) * 100}%`;
                                if (!shouldStop) {
                                    await isChatGptReady();
                                }
                                if (shouldStop) {
                                    shouldStop = false;
                                    break;
                                }
                            }
                            progressBar.style.backgroundColor = "#19C37D";

                            // After file uploading is finished or stopped
                            submitButton.textContent = "上传文件"; // change the button text back to 'Upload file'
                            submitButton.style = styles.button; // change the button color back to blue
                            progressBar.style.width = "0%";
                        };
                    };

                    input.addEventListener("change", fileChangeListener);
                    input.click();
                });

                // aboutButton click
                aboutButton.addEventListener("click", () => {
                    // 生成一个半透明的背景
                    const overlay = document.createElement("div");
                    overlay.style = `
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(5px);
`;

          // 生成一个带有毛玻璃效果的窗口
          const modal = document.createElement("div");
          modal.style = `
  width: 50%;
  padding: 20px;
  background: rgba(225, 225, 225, 0.7);
  border-radius: 10px;
  backdrop-filter: blur(10px);
  box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
  border: 1px solid rgba(255, 255, 255, 0.18);
  text-align: center;
`;

          // 添加帮助文字
          const text = document.createElement("p");
          text.innerHTML =
              "<div>【ChatGPT自动上传插件】</div>" +
              '<div style="text-align: left;"><br>1.选择参数:设定单次发送句子的数量和延迟时间。<br>' +
              "2.设定提示词(可选):点击“前置提示词”按钮,选择一个文件作为固定提示词。<br>" +
              "3.上传文件:点击“上传文件”按钮,选择一个文本。<br>" +
              "4.停止上传:在文件上传过程中,随时可以点击“停止”按钮来停止上传。<br>" +
              "5.查看进度:屏幕上的进度条显示上传的进度。<br><br>" +
              "【注意】上传文件只支持TXT格式,上传文件后会稍微卡顿几秒。<br><br>" +
              "作者:老陆(微信:laolu2045)<br>" +
              "想加入AI群一起学习或有其他建议请联系我。</div>";
          modal.appendChild(text);

          // 添加关闭按钮
          const closeButton = document.createElement("button");
          closeButton.textContent = "关闭";
          closeButton.style = `
  display: block;
  margin-top: 20px;
  margin-left: auto;
  margin-right: auto;
  padding: 10px 20px;
  border: none;
  border-radius: 5px;
  background: linear-gradient(90deg, rgba(235, 52, 52, 1) 0%, rgba(236, 116, 116, 1) 100%);
  box-shadow: 0px 3px 15px rgba(0,0,0,0.2);
  color: white;
  cursor: pointer;
  transition: background 0.5s;
`;
          closeButton.addEventListener("mouseover", () => {
              closeButton.style.background = "rgba(236, 116, 116, 1)";
          });
          closeButton.addEventListener("mouseout", () => {
              closeButton.style.background =
                  "linear-gradient(90deg, rgba(235, 52, 52, 1) 0%, rgba(236, 116, 116, 1) 100%)";
          });
          closeButton.addEventListener("click", () => {
              document.body.removeChild(overlay);
          });
          modal.appendChild(closeButton);

          // 将窗口添加到背景上,然后将背景添加到文档上
          overlay.appendChild(modal);
          document.body.appendChild(overlay);
      });
    }
  }, 100);
};

    // Initialize MutationObserver
    const targetNode = document.body;
    const config = { childList: true, subtree: true };

    const callback = function (mutationsList, observer) {
        for (const mutation of mutationsList) {
            if (mutation.type === "childList") {
                const targetElement = document.querySelector(
                    ".pb-3.pt-2"
                );

                if (
                    targetElement !== null &&
                    !targetElement.querySelector(".buttonWrapper[data-inserted='true']")
                ) {
                    initPlugin();
                }
            }
        }
    };

    // 使用MutationObserver来监听DOM变化
    const observer = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
            // 检查是否有新节点被添加
            if (mutation.addedNodes.length) {
                initPlugin();
            }
        });
    });

    // 选项配置
    const observerConfig = {
        attributes: true,
        childList: true,
        characterData: true,
        subtree: true,
    };

    // 监听document.body的变化
    observer.observe(document.body, observerConfig);

})();