ChatChain

works like langchain but with ChatGPT(WIP), only support Translate input to English on ChatChain with Google Translate now

  1. // ==UserScript==
  2. // @name ChatChain
  3. // @namespace ChatChain
  4. // @version 1.0
  5. // @description works like langchain but with ChatGPT(WIP), only support Translate input to English on ChatChain with Google Translate now
  6. // @author temberature@gmail.com
  7. // @match https://chat.openai.com/*
  8. // @grant GM_xmlhttpRequest
  9. // @run-at document-idle
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15. let monitorReply2TranslateBack = false, monitorReply2quest = false;
  16. let translateMode = true;
  17. let form, submitButton, inputField, submitButtonClone, inputFieldClone;
  18. interceptSubmitBtn();
  19.  
  20. const observer = new MutationObserver(mutations => {
  21. mutations.forEach(mutation => {
  22. if (mutation.type === 'childList') {
  23. console.log(mutation.addedNodes)
  24. mutation.addedNodes.forEach(addedNode => {
  25. if (addedNode.nodeType === Node.ELEMENT_NODE && ((addedNode.classList.contains('overflow-hidden') && addedNode.classList.contains('w-full') && addedNode.classList.contains('h-full') && addedNode.classList.contains('relative')))) {
  26. interceptSubmitBtn();
  27. }
  28. if (addedNode.nodeType === Node.ELEMENT_NODE && (addedNode.classList.contains('px-2') &&
  29. addedNode.classList.contains('py-10') &&
  30. addedNode.classList.contains('relative') &&
  31. addedNode.classList.contains('w-full') &&
  32. addedNode.classList.contains('flex') &&
  33. addedNode.classList.contains('flex-col') &&
  34. addedNode.classList.contains('h-full'))) {
  35.  
  36. interceptSubmitBtn();
  37.  
  38. }
  39.  
  40. });
  41.  
  42. mutation.removedNodes.forEach(removedNode => {
  43. if (removedNode.nodeType === Node.ELEMENT_NODE && removedNode.classList.contains('btn') && removedNode.classList.contains('flex') && removedNode.classList.contains('justify-center') && removedNode.classList.contains('gap-2') && removedNode.classList.contains('btn-neutral') && removedNode.classList.contains('border-0') && removedNode.classList.contains('md:border') && removedNode.textContent.includes('Stop generating')) {
  44. const proseElements = document.querySelectorAll('.prose');
  45. const lastProseElement = proseElements[proseElements.length - 1];
  46. const form = document.querySelector('form.stretch');
  47. const submitButton = form.querySelector('div div:nth-child(2) > button');
  48. const inputField = form.querySelector('div:nth-child(2) textarea');
  49. if (monitorReply2quest) {
  50. quest(lastProseElement.textContent);
  51.  
  52. } else if (monitorReply2TranslateBack) {
  53. translateBack(lastProseElement.textContent);
  54. }
  55. }
  56. })
  57. }
  58. });
  59. });
  60.  
  61. observer.observe(document.body, { childList: true, subtree: true });
  62. // 定义翻译函数,用于翻译给定的文本
  63. const translateText = async (text) => {
  64. const url = 'https://translate.googleapis.com/translate_a/single?client=gtx&sl=zh-CN&tl=en&dt=t&q=' + encodeURI(text);
  65. const response = await fetch(url);
  66. const data = await response.json();
  67. return data[0][0][0];
  68. };
  69.  
  70. function interceptSubmitBtn() {
  71.  
  72. form = document.querySelector('form.stretch');
  73. submitButton = form.querySelector('div div:nth-child(2) > button');
  74. inputField = form.querySelector('div:nth-child(2) textarea');
  75. // 拦截表单提交事件,翻译输入内容并替换输入框中的内容
  76. if (!submitButton || !inputField) {
  77. return;
  78. }
  79.  
  80. submitButton.addEventListener('click', handleSubmit, { useCapture: true });
  81.  
  82. inputField.addEventListener("keydown", handleEnterDown, { useCapture: true });
  83. inputField.addEventListener("keyup", async function (event) {
  84. if (event.key === "Enter") {
  85. event.preventDefault();
  86. event.stopImmediatePropagation();
  87. }
  88. });
  89. inputField.addEventListener("keypress", async function (event) {
  90. if (event.key === "Enter") {
  91. event.preventDefault();
  92. event.stopImmediatePropagation();
  93. }
  94. });
  95.  
  96.  
  97. }
  98. async function handleEnterDown(event) {
  99. if (event.key === "Enter") {
  100. handleSubmit(event);
  101. }
  102. }
  103. async function handleSubmit(event) {
  104. const stage = submitButton.getAttribute('data-stage')
  105. const texts = inputField.value.split('|');
  106.  
  107. if ((stage && stage != 0) || !translateMode) {
  108. return;
  109. } else if (texts.length > 1 && ['默认模式', 'default', 'd'].includes(texts[0])) {
  110. inputField.value = texts[1];
  111. return;
  112. } else if (texts.length > 1 && ['切换模式', 'toggle', 't'].includes(texts[0])) {
  113. translateMode = !translateMode;
  114. inputField.value = texts[1];
  115. return;
  116. }
  117. event.preventDefault();
  118. event.stopImmediatePropagation();
  119. if (!+stage) {
  120. if (!monitorReply2quest && !monitorReply2TranslateBack) {
  121. translate();
  122. }
  123. }
  124.  
  125. }
  126.  
  127. async function translate() {
  128. submitButton.setAttribute('data-stage', 'translating');
  129.  
  130. const inputText = inputField.value.trim();
  131. if (inputText.length === 0) {
  132. return;
  133. }
  134. inputField.value = "翻译中ing";
  135. const translatedText = await translateText(inputText);
  136. inputField.value = translatedText;
  137.  
  138. submitButton.click();
  139.  
  140. monitorReply2TranslateBack = true;
  141. }
  142.  
  143. function quest(reply) {
  144. submitButton.setAttribute('data-stage', 'questing');
  145.  
  146. if (reply.length === 0) {
  147. return;
  148. }
  149.  
  150. inputField.value = 'answer using English, as specific as possible: "' + reply + '"';
  151.  
  152. submitButton.click();
  153. monitorReply2quest = false;
  154. monitorReply2TranslateBack = true;
  155. }
  156. function translateBack(reply) {
  157. submitButton.setAttribute('data-stage', 'translateBacking');
  158. inputField.value = '翻译成中文,只返回中文: "' + reply + '"';
  159. submitButton.click();
  160. monitorReply2TranslateBack = false;
  161. submitButton.setAttribute('data-stage', 0);
  162. }
  163.  
  164. })();