ChatGPT LaTeX Auto Render (OpenAI, you, bing, etc.)

自动渲染 ChatGPT 页面 (OpenAI, new bing, you 等) 上的 LaTeX 数学公式。

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

  1. // ==UserScript==
  2. // @name ChatGPT LaTeX Auto Render (OpenAI, you, bing, etc.)
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.4.3
  5. // @author Scruel
  6. // @homepage https://github.com/scruel/tampermonkey-scripts
  7. // @description Auto typeset LaTeX math formulas on ChatGPT pages (OpenAI, new bing, you, etc.).
  8. // @description:zh-CN 自动渲染 ChatGPT 页面 (OpenAI, new bing, you 等) 上的 LaTeX 数学公式。
  9. // @match https://chat.openai.com/*
  10. // @match https://you.com/search?*&tbm=youchat*
  11. // @match https://www.bing.com/search?*
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15. 'use strict';
  16.  
  17. const _parsed_mark = '_sc_parsed';
  18.  
  19. function queryAddNoParsed(query) {
  20. return query + ":not([" + _parsed_mark + "])";
  21. }
  22.  
  23. function prepareScript() {
  24. window._sc_typeset = () => {
  25. if (window._sc_typesetting) {
  26. return;
  27. }
  28. window._sc_typesetting = true;
  29. try {
  30. const messages = window._sc_get_messages();
  31. messages.forEach((element) => {
  32. element.setAttribute(_parsed_mark,'');
  33. MathJax.typesetPromise([element]);
  34. });
  35. } catch (ignore) {
  36. }
  37. window._sc_typesetting = false;
  38. }
  39.  
  40. const commonCheck = (query) => {
  41. const submitButton = document.querySelector(query);
  42. return submitButton && !submitButton.disabled;
  43. }
  44.  
  45. if (window.location.host == "www.bing.com") {
  46. window._sc_get_messages = () => {
  47. const elements = [];
  48. const allChatTurn = document.querySelector("#b_sydConvCont > cib-serp").shadowRoot.querySelector("#cib-conversation-main").shadowRoot.querySelectorAll("#cib-chat-main > cib-chat-turn");
  49. if (allChatTurn.length == 0) {
  50. return;
  51. }
  52. const allCibMeg = allChatTurn[allChatTurn.length - 1].shadowRoot.querySelector("cib-message-group.response-message-group").shadowRoot.querySelectorAll("cib-message");
  53. allCibMeg.forEach((cibMeg) => {
  54. const element = cibMeg.shadowRoot.querySelector(queryAddNoParsed("cib-shared"));
  55. if (element) {
  56. element.style.display = 'unset';
  57. elements.append(element);
  58. }
  59. });
  60. return elements;
  61. }
  62. window._sc_isAnswerPrepared = () => {
  63. const actionBarParent = document.querySelector("#b_sydConvCont > cib-serp");
  64. if (!actionBarParent) {
  65. return false;
  66. }
  67. const actionBar = actionBarParent.shadowRoot.querySelector("#cib-action-bar-main");
  68. return actionBar && !actionBar.hasAttribute('cancelable');
  69. }
  70. }
  71. else if (window.location.host == "you.com") {
  72. window._sc_get_messages = () => {
  73. return document.querySelectorAll(queryAddNoParsed('#chatHistory div[data-testid="youchat-answer"]'));
  74. }
  75. window._sc_isAnswerPrepared = () => {
  76. return commonCheck('main div[data-testid="youchat-input"] textarea+button');
  77. }
  78. }
  79. else if (window.location.host == "chat.openai.com") {
  80. window._sc_get_messages = () => {
  81. return document.querySelectorAll(queryAddNoParsed("div.w-full div.text-base div.items-start"));
  82. }
  83. window._sc_isAnswerPrepared = () => {
  84. return commonCheck('main form textarea+button');
  85. }
  86. }
  87. }
  88.  
  89. function renderTrigger() {
  90. setTimeout(renderLatex, window.renderDelay);
  91. }
  92.  
  93. function renderLatex() {
  94. if (window._sc_isAnswerPrepared()) {
  95. // console.log("Rendering...")
  96. window._sc_typeset();
  97. }
  98. renderTrigger();
  99. }
  100.  
  101. async function addScript(url) {
  102. const scriptElement = document.createElement('script');
  103. const headElement = document.getElementsByTagName('head')[0] || document.documentElement;
  104. if (!headElement.appendChild(scriptElement)) {
  105. // Prevent appendChild overwritten problem.
  106. headElement.append(scriptElement);
  107. }
  108. scriptElement.src = url;
  109.  
  110. await waitScriptLoaded();
  111. }
  112.  
  113. function waitScriptLoaded() {
  114. return new Promise(async (resolve, reject) => {
  115. const resolver = () => {
  116. if (MathJax.hasOwnProperty('typeset')) {
  117. resolve();
  118. return;
  119. }
  120. if (window._sc_ChatLatex.loadCount > 100) {
  121. setTipsElementText("Failed to load MathJax, try refresh.");
  122. reject("Failed to load MathJax, try refresh.");
  123. return;
  124. }
  125. window._sc_ChatLatex.loadCount += 1;
  126. window.setTimeout(resolver, 200);
  127. }
  128. resolver();
  129. });
  130. }
  131.  
  132. function setTipsElementText(text) {
  133. window._sc_ChatLatex.tipsElement.innerHTML = text;
  134. }
  135.  
  136. function showTipsElement() {
  137. const tipsElement = window._sc_ChatLatex.tipsElement;
  138. tipsElement.style.position = "fixed";
  139. tipsElement.style.left = "10px";
  140. tipsElement.style.bottom = "10px";
  141. tipsElement.style.background = '#333';
  142. tipsElement.style.color = '#fff';
  143. document.body.appendChild(tipsElement);
  144. }
  145.  
  146. function hideTipsElement(timeout=3) {
  147. window.setTimeout(() => {window._sc_ChatLatex.tipsElement.hidden=true}, 3000);
  148. }
  149.  
  150. (async function() {
  151. window._sc_ChatLatex = {
  152. tipsElement: document.createElement("div"),
  153. loadCount: 0
  154. }
  155. window.MathJax = {
  156. tex: {
  157. inlineMath: [['$', '$'], ['\\(', '\\)']],
  158. displayMath : [['$$', '$$']]
  159. }
  160. };
  161.  
  162. showTipsElement();
  163. setTipsElementText("Loading MathJax...");
  164. await addScript('https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js');
  165. setTipsElementText("MathJax Loaded.");
  166. hideTipsElement();
  167.  
  168. prepareScript();
  169. window.renderDelay = 1000;
  170. renderTrigger();
  171. })();