// ==UserScript== // @name ChatGPT: 英文翻譯工具 // @description 自動修改textarea後送出、論文翻譯、語句更改、文法檢查、字典 // @version 1.5.0 // @source https://github.com/iewihc/GPTTranslateScript/blob/main/userSrcipt.js // @namespace https://github.com/iewihc/GPTTranslateScript/blob/main/userSrcipt.js // @website https://fullstackladder.dev/ // @author Chi-Wei Lin // @run-at document-end // @license MIT // @match *://chat.openai.com/chat* // @icon https://www.google.com/s2/favicons?sz=64&domain=openai.com // @downloadURL none // ==/UserScript== // 添加以下代码来引入 React 库 const reactScript = document.createElement('script'); reactScript.src = 'https://unpkg.com/react/umd/react.development.js'; document.head.appendChild(reactScript); const reactDOMScript = document.createElement('script'); reactDOMScript.src = 'https://unpkg.com/react-dom/umd/react-dom.development.js'; document.head.appendChild(reactDOMScript); const defaultBottomOptions = [ { text: 'Originial', template:`請你忽略上述我們所有的對話,從現在開始您是一名優秀的翻譯人員,我會給您文章, 並分成三個部份回答我 第一個部份請幫我翻譯成流暢的繁體中文,並在標題輸入「第一部分」 第二個部份使用項目符號幫我列出繁體中文和英文的摘要,五個項目就可以了,中文與英文麻煩幫我同時列在同一個點上,其格式為:中文句子 (English Sentence),並在標題輸入「第二部分」。 第三個部份請你幫我整理文章中除了地名和人名之外的專業術語,也應包括英文和繁體中文,用逗號分隔。比如說:蘋果 (Apple), 香蕉 (Banana)的格式,並在標題輸入「第三部分」。 文章內容如下: {replace_text} ` }, { text: '翻譯論文', template:`請你忽略上述我們所有的對話,從現在開始您是一名優秀的翻譯人員,我會給您文章, 並分成三個部份回答我 第一個部份請幫我翻譯成流暢的繁體中文,並在標題輸入「翻譯」 文章內容如下: {replace_text} ` }, { text: '項目摘要', template:`請你繼續根據本篇文章,使用項目符號幫我列出繁體中文和英文的摘要,中文與英文麻煩幫我同時列在同一個點上,其格式為:中文句子 (English Sentence),並在標題輸入「項目摘要」。` }, { text: '深難詞彙', template: `請你繼續根據本篇文章,幫我整理出這篇文章的深難字彙,需要包含繁體中文和英文。` }, { text: '一句話概述', template: `請你用一句話簡短的概述本篇文章在講什麼,需要英文和繁體中文,其格式為:中文句子 (English Sentence) 並在標題輸入「概述」` } // { text: 'PARAPHRASE', template: `請你幫我使用英文Paraphrase這段句子,並使用項目符號說明您修改的內容: {{replace_text}} `}, // { // text: 'DICTIONARY', template: `請您幫我列出這個單字的中文和英文並且 1. 該單字的兩個例句,例句需要包含繁體中文和英文 2. 該單字的五個vocabulary collocations用法 2. 該單字不同詞性,包含其名詞、代名詞、形容詞、動詞、副詞 3. 該單字同義詞和反義詞,單字為:{replace_text} ` // }, // { // text: 'GRAMMAR', template:`請幫此段 {{replace_text}} 1. 翻譯成繁體中文 2. 請修正這段句子的文法錯誤,並使用項目符號說明您修改的內容 3. 請使用英語,將此句翻寫為更加學術` // }, // { // text: 'TESTCASE', template: `你現在是一個程式語言專家,我有一段程式碼 {{replace_text}} ,請幫我寫一個測試,請至少提供五個測試案例,同時要包含到極端的狀況,讓我能夠確定這段程式碼的輸出是正確的。`, // }, // { // text: 'REFACTOR', template: `你現在是一個 Clean Code 專家,我有以下的程式碼,請用更乾淨簡潔的方式改寫,讓我的同事們可以更容易維護程式碼。另外,也解釋為什麼你要這樣重構,讓我能把重構的方式的說明加到 Pull Request 當中。 {{replace_text}`, // } ]; const commandOptions = [ { cmd: "/寫程式", prompt: `你現在是一個 程式語言 專家,請幫我用 程式語言 寫一個函式,它需要做到 某個功能 ` }, { cmd: "/解讀程式碼", prompt: `你現在是一個 程式語言 專家,請告訴我以下的程式碼在做什麼。 附上程式碼 ` }, { cmd: "/重構程式碼", prompt: `你現在是一個 Clean Code 專家,我有以下的程式碼,請用更乾淨簡潔的方式改寫,讓我的同事們可以更容易維護程式碼。另外,也解釋為什麼你要這樣重構,讓我能把重構的方式的說明加到 Pull Request 當中。 附上程式碼 ` }, { cmd: "/解bug", prompt: `你現在是一個 程式語言 專家,我有一段程式碼,我預期這段程式碼可以 做到某個功能,只是它通過不了 測試案例 這個測試案例。請幫我找出我哪裡寫錯了,以及用正確的方式改寫。附上程式碼 ` }, { cmd: "/寫測試", prompt: `你現在是一個 程式語言 專家,我有一段程式碼 附上程式碼,請幫我寫一個測試,請至少提供五個測試案例,同時要包含到極端的狀況,讓我能夠確定這段程式碼的輸出是正確的。 ` }, { cmd: "/寫Regex", prompt: `你現在是一個 Regex 專家,請幫我寫一個 Regex ,它能夠把 [需求] ` }, ] // 預設要顯示的按鈕和文字範本 const fillAndSubmitText = (test) => { // 取得 textarea 元素並設定 value const textarea = document.querySelector("textarea"); // 觸發 input 事件 textarea.value = test; // 取得送出按鈕並點擊 textarea.dispatchEvent(new Event("input", { bubbles: true })); const button = textarea.parentElement.querySelector("button:last-child"); button.click(); }; // 透過傳入的文字來替換 textarea 中的指定文字後再提交 const addTextToTextarea = (test) => { const textarea = document.querySelector("textarea"); const text = textarea.value; const newText = test.replace('{replace_text}', text); fillAndSubmitText(newText); }; // 新增底部按鈕,並設定格式,點擊按鈕時會將對應的文字插入到 textarea 中 const addBottomButtonAndFormatting = ()=>{ setTimeout(function() { // 建立包含按鈕的容器 let btnDivContainer = document.createElement('div'); // 建立每個按鈕的 HTML 元素並設定樣式、文字、點擊事件 defaultBottomOptions.forEach((item) => { const button = document.createElement("button"); button.style.border = "1px solid #d1d5db"; button.style.borderRadius = "5px"; button.style.padding = "0.5rem 1rem"; button.style.margin = "0.5rem"; button.innerText = item.text; button.addEventListener('click', function() { addTextToTextarea(item.template); }); btnDivContainer.append(button); }); // 找到底部的 div 元素,如果存在就清空原有內容,並加入按鈕容器 const bottomDiv = document.querySelector('.px-3.pt-2.pb-3.text-center.text-xs.text-black\\/50.dark\\:text-white\\/50.md\\:px-4.md\\:pt-3.md\\:pb-6'); if (bottomDiv) { bottomDiv.innerHTML = ''; bottomDiv.appendChild(btnDivContainer); } }, 5000) } const addSideButton = () =>{ const textarea = document.querySelector("textarea"); // 監聽 textarea 輸入 textarea.addEventListener("keydown", function(e) { // 當輸入 "/" 時彈出選項 if (e.key === "/" && e.target === textarea) { e.preventDefault(); // 防止出現 "/" showOptions(); } else if (e.key === "Escape" && document.getElementById("optionsDiv")) { document.getElementById("optionsDiv").remove(); } }); // 隱藏選項框 const hideOptions = () => { const optionsDiv = document.getElementById("optionsDiv"); if (optionsDiv) { optionsDiv.parentNode.removeChild(optionsDiv); } }; // 彈出選項 const showOptions = () => { // 創建彈出框 const optionsDiv = document.createElement("div"); optionsDiv.id = "optionsDiv"; optionsDiv.style.position = "absolute"; optionsDiv.style.top = `${textarea.offsetTop - optionsDiv.offsetHeight}px`; optionsDiv.style.left = textarea.offsetLeft + "px"; optionsDiv.style.backgroundColor = "#FFFFFF80"; optionsDiv.style.border = "1px solid rgb(209, 213, 219)"; optionsDiv.style.borderRadius = "5px"; optionsDiv.style.padding = "0.5rem 1rem"; optionsDiv.style.margin = "0.5rem"; optionsDiv.style.height = "100px"; // 設定預設高度 // 創建選項 for (const {cmd} of commandOptions) { const optionDiv = document.createElement("div"); optionDiv.style.cursor = "pointer"; optionDiv.style.margin = "0.5rem"; optionDiv.style.padding = "0.5rem 1rem"; optionDiv.style.border = "1px solid rgb(209, 213, 219)"; optionDiv.style.borderRadius = "5px"; optionDiv.style.backgroundColor = "#FFFFFF80"; optionDiv.style.color = "#000"; optionDiv.textContent = cmd; optionsDiv.appendChild(optionDiv); // 選項被選中 optionDiv.addEventListener("click", () => { output(cmd); selectedOptionIndex = -1; // 將選中的選項索引重設為-1 optionsDiv.remove(); }); // 選項被選中時反藍背景 optionDiv.addEventListener("mouseenter", () => { optionDiv.style.backgroundColor = "#edf2f7"; }); optionDiv.addEventListener("mouseleave", () => { optionDiv.style.backgroundColor = "#fff"; }); } // 將彈出框添加到文檔中 document.body.appendChild(optionsDiv); // 選擇選項使用鍵盤上下鍵 let selectedOptionIndex = -1; const selectOption = (index) => { const optionDivs = optionsDiv.querySelectorAll("div"); if (index < 0) { index = optionDivs.length - 1; } else if (index >= optionDivs.length) { index = 0; } selectedOptionIndex = index; // 設定被選中的選項背景為反藍色 optionDivs.forEach((optionDiv) => { optionDiv.style.backgroundColor = "#fff"; }); optionDivs[selectedOptionIndex].style.backgroundColor = "#4299e1"; }; selectOption(selectedOptionIndex); textarea.addEventListener("keydown", (e) => { const optionDivs = optionsDiv.querySelectorAll("div"); if (e.key === "ArrowUp") { selectOption(selectedOptionIndex - 1); } else if (e.key === "ArrowDown") { selectOption(selectedOptionIndex + 1); } else if (e.key === "Tab" && selectedOptionIndex >= 0) { output(optionDivs[selectedOptionIndex].textContent); optionsDiv.remove(); } }); // 監聽點擊文檔,如果點擊其他地方則移除 function removeOptions() { optionsDiv.remove(); document.removeEventListener("click", removeOptions); } document.addEventListener("click", removeOptions); // 監聽鍵盤事件,透過上下鍵來選擇option let selectedIndex = 0; const optionDivs = optionsDiv.querySelectorAll("div"); optionDivs[selectedIndex].classList.add("selected"); document.addEventListener("keydown", (e) => { switch (e.key) { case "ArrowUp": selectedIndex = Math.max(selectedIndex - 1, 0); break; case "ArrowDown": selectedIndex = Math.min(selectedIndex + 1, optionDivs.length - 1); break; case "Tab": output(optionDivs[selectedIndex].textContent); e.preventDefault(); break; default: return; } // 選擇 option 後反藍背景 optionDivs.forEach((optionDiv, index) => { if (index === selectedIndex) { optionDiv.classList.add("selected"); } else { optionDiv.classList.remove("selected"); } }); }); // 輸出選項對應的文字到 textarea 中 const output = (cmd) => { const selectedOption = commandOptions.find((option) => option.cmd === cmd); if (!selectedOption) { return; } const outputText = selectedOption.prompt; textarea.value = outputText; textarea.style.resize = "vertical"; textarea.style.overflow = "auto"; hideOptions(); // 重置選項 selectedOptionIndex = -1; selectOption(selectedOptionIndex); }; }; } (function () { "use strict"; addBottomButtonAndFormatting(); addSideButton(); })();