ChatGPT Model Switcher

Switch ChatGPT model mid-chat

  1. // ==UserScript==
  2. // @name ChatGPT Model Switcher
  3. // @namespace https://ucm.dev/
  4. // @version 1.0
  5. // @description Switch ChatGPT model mid-chat
  6. // @author GPT-4, GPT-3.5, Copilot, and Sam Watkins <sam@ucm.dev>
  7. // @match *://chat.openai.com/*
  8. // @grant none
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. // This user script adds a model switcher to the OpenAI chat interface,
  13. // allowing users to switch between different AI models when conversing.
  14.  
  15. (function () {
  16.  
  17. "use strict";
  18.  
  19. // Define various constants, such as available models,
  20. // URLs for API calls, messages, and selectors for DOM elements.
  21.  
  22. const MODELS = [
  23. {
  24. "name": "text-davinci-002-render-sha",
  25. "label": "Default (GPT-3.5)"
  26. },
  27. {
  28. "name": "text-davinci-002-render-paid",
  29. "label": "Legacy (GPT-3.5)"
  30. },
  31. {
  32. "name": "gpt-4",
  33. "label": "GPT-4"
  34. }
  35. ];
  36.  
  37. const URLS = {
  38. conversation: "https://chat.openai.com/backend-api/conversation",
  39. };
  40.  
  41. const MESSAGES = {
  42. modelUnavailable: "The previous model used in this conversation is unavailable. " +
  43. "We've switched you to the latest default model",
  44. modelChanged: "Now using ",
  45. };
  46.  
  47. const SELECTORS = {
  48. buttons: "#__next main form > div div:nth-of-type(1)",
  49. messagesContainer: ".flex-col.items-center",
  50. modelUnavailableMessage: ".flex-col.items-center .text-center",
  51. chatGPTModelSelect: "#__next button[data-headlessui-state]",
  52. };
  53.  
  54. const IDS = {
  55. switcher: "chatgpt-model-switcher",
  56. };
  57.  
  58. // Utility functions
  59. const $ = (selector) => document.querySelector(selector);
  60. const $$ = (selector) => document.querySelectorAll(selector);
  61. const $id = (id) => document.getElementById(id);
  62. const $create = (element) => document.createElement(element);
  63.  
  64. // Update the model parameter in the request body to match the
  65. // selected model in the UI.
  66. function updateModelParameter(originalRequest) {
  67. // Parse the request body
  68. const requestData = JSON.parse(originalRequest.body);
  69.  
  70. // Make sure the request has a model parameter
  71. if (!requestData.model) return originalRequest;
  72.  
  73. // Make sure the model switcher exists in the UI
  74. const modelSwitcher = $id(IDS.switcher);
  75. if (!modelSwitcher) return originalRequest;
  76.  
  77. // Update the model parameter based on the selected option in the UI
  78. requestData.model = modelSwitcher.value;
  79.  
  80. // Modify the request to include the updated model parameter
  81. const updatedRequest = {
  82. ...originalRequest,
  83. body: JSON.stringify(requestData),
  84. };
  85.  
  86. return updatedRequest;
  87. }
  88.  
  89. // Check if the request is a conversation request.
  90. function isConversationRequest(requestArgs) {
  91. return requestArgs[0] &&
  92. requestArgs[0] === URLS.conversation &&
  93. requestArgs[1] &&
  94. requestArgs[1].method === "POST" &&
  95. requestArgs[1].body;
  96. }
  97.  
  98. // Replace the original fetch function with a new function that
  99. // updates the model parameter in the request before sending it.
  100. const originalFetch = window.fetch;
  101. window.fetch = async function () {
  102. if (isConversationRequest(arguments)) {
  103. arguments[1] = updateModelParameter(arguments[1]);
  104. }
  105. return await originalFetch.apply(this, arguments);
  106. };
  107.  
  108. // Replace the model unavailable message with a message indicating
  109. // that the model has been changed.
  110. function replaceModelUnavailableMessage() {
  111. for (const element of $$(SELECTORS.modelUnavailableMessage)) {
  112. if (element.textContent !== MESSAGES.modelUnavailable) continue;
  113. const newModel = $id(IDS.switcher).value;
  114. const newModelLabel = MODELS.find((model) => model.name === newModel).label;
  115. element.textContent = MESSAGES.modelChanged + newModelLabel;
  116. }
  117. }
  118.  
  119. // Synchronize the custom model switcher with the original model selector.
  120. // Update the custom model switcher's value to match the currently selected
  121. // model in the original model selector.
  122. function syncCustomModelSwitcher() {
  123. const chatGPTModelSelect = $(SELECTORS.chatGPTModelSelect);
  124. const modelLabel = chatGPTModelSelect.innerText.split("\n")[1];
  125. const customModelSwitcher = $id(IDS.switcher);
  126.  
  127. for (const model of MODELS) {
  128. if (model.label !== modelLabel) continue;
  129. customModelSwitcher.value = model.name;
  130. break;
  131. }
  132. }
  133.  
  134. // Initialize the model switcher: Add it to the chat interface, watch the
  135. // original model selector, and watch for the model unavailable message.
  136. function initModelSwitcher() {
  137. if ($id(IDS.switcher)) return;
  138.  
  139. const buttons = $(SELECTORS.buttons);
  140.  
  141. // Create the model switcher
  142. const modelSwitcher = $create("select");
  143. modelSwitcher.id = IDS.switcher;
  144. modelSwitcher.classList.add("btn", "flex", "gap-2", "justify-center", "btn-neutral");
  145. for (const model of MODELS) {
  146. const option = $create("option");
  147. option.value = model.name;
  148. option.textContent = model.label;
  149. modelSwitcher.appendChild(option);
  150. }
  151.  
  152. // Add the model switcher to the button bar
  153. buttons.appendChild(modelSwitcher);
  154.  
  155. // Initialize a MutationObserver to watch the messages container
  156. for (const element of $$(SELECTORS.messagesContainer)) {
  157. new MutationObserver(replaceModelUnavailableMessage).observe(element, { childList: true, subtree: true });
  158. }
  159. }
  160.  
  161. let chatGPTModelSelectObserver = null;
  162.  
  163. // Watch ChatGPT's model selector
  164. function watchChatGPTModelSelect() {
  165. if (chatGPTModelSelectObserver) return;
  166. const chatGPTModelSelect = $(SELECTORS.chatGPTModelSelect);
  167. chatGPTModelSelectObserver = new MutationObserver(syncCustomModelSwitcher);
  168. chatGPTModelSelectObserver.observe(chatGPTModelSelect, { childList: true, characterData: true, subtree: true });
  169. }
  170.  
  171. // Check if the chat interface has been loaded, and call the
  172. // initModelSwitcher function when it is detected.
  173. function chatInterfaceChanged() {
  174. if ($(SELECTORS.buttons) && $(SELECTORS.messagesContainer)) {
  175. initModelSwitcher();
  176. }
  177. if ($(SELECTORS.chatGPTModelSelect)) {
  178. watchChatGPTModelSelect();
  179. }
  180. }
  181.  
  182. // Observe mutations to the body element, and call the
  183. // initModelSwitcher function when the chat interface is detected.
  184. new MutationObserver(chatInterfaceChanged).observe(document.body, { childList: true, subtree: true });
  185.  
  186. })();