ChatGPT Model Switcher: Toggle on/off 4o-mini Improved Version

Injects a button allowing you to toggle on/off 4o-mini during the chat

  1. // ==UserScript==
  2. // @name ChatGPT Model Switcher: Toggle on/off 4o-mini Improved Version
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.24.1
  5. // @description Injects a button allowing you to toggle on/off 4o-mini during the chat
  6. // @match *://chatgpt.com/*
  7. // @author d0gkiller87, improved UI by Aoshi Xu
  8. // @license MIT
  9. // @grant unsafeWindow
  10. // @grant GM.setValue
  11. // @grant GM.getValue
  12. // @run-at document-idle
  13. // ==/UserScript==
  14.  
  15. (async function() {
  16. 'use strict';
  17.  
  18. class ModelSwitcher {
  19. constructor( useMini = true ) {
  20. this.useMini = useMini;
  21. this.containerSelector = '#composer-background div:nth-of-type(2) div:first-child';
  22. }
  23.  
  24. hookFetch() {
  25. const originalFetch = unsafeWindow.fetch;
  26. unsafeWindow.fetch = async ( resource, config = {} ) => {
  27. if (
  28. resource === 'https://chatgpt.com/backend-api/conversation' &&
  29. config.method === 'POST' &&
  30. config.headers &&
  31. config.headers['Content-Type'] === 'application/json' &&
  32. config.body
  33. ) {
  34. if ( this.useMini ) {
  35. const body = JSON.parse( config.body );
  36. body.model = 'gpt-4o-mini';
  37. config.body = JSON.stringify( body );
  38. }
  39. }
  40. return originalFetch( resource, config );
  41. };
  42. }
  43.  
  44. injectToggleButtonStyle() {
  45. // Credit: https://webdevworkshop.io/code/css-toggle-button/
  46. if ( !document.getElementById( 'toggleCss' ) ) {
  47. const styleNode = document.createElement( 'style' );
  48. styleNode.id = 'toggleCss';
  49. styleNode.type = 'text/css';
  50. styleNode.textContent = `.toggle {
  51. position: relative;
  52. display: inline-block;
  53. width: 2.5rem;
  54. height: 1.5rem;
  55. background-color: hsl(0deg 0% 40%);
  56. border-radius: 25px;
  57. cursor: pointer;
  58. transition: background-color 0.2s ease-in;
  59. }
  60. .toggle::after {
  61. content: '';
  62. position: absolute;
  63. width: 1.4rem;
  64. left: 0.1rem;
  65. height: calc(1.5rem - 2px);
  66. top: 1px;
  67. background-color: white;
  68. border-radius: 50%;
  69. transition: all 0.2s ease-out;
  70. }
  71. .hide-me {
  72. opacity: 0;
  73. height: 0;
  74. width: 0;
  75. }`;
  76. document.head.appendChild( styleNode );
  77. }
  78. }
  79.  
  80. getContainer() {
  81. return document.querySelector( this.containerSelector );
  82. }
  83.  
  84. injectToggleButton( container = null ) {
  85. console.log( 'inject' );
  86. if ( !container ) container = this.getContainer();
  87. if ( !container ) {
  88. console.error( 'container not found!' );
  89. return;
  90. }
  91. if ( container.querySelector( '#cb-toggle' ) ) {
  92. console.log( '#cb-toggle already exists' );
  93. return;
  94. }
  95. container.classList.add('items-center');
  96. const button = document.createElement('button');
  97. button.id = 'cb-toggle';
  98. button.className = 'flex h-8 min-w-8 items-center justify-center rounded-lg p-1 text-xs font-semibold hover:bg-black/10 focus-visible:outline-black dark:focus-visible:outline-white';
  99. button.setAttribute('aria-pressed', 'false');
  100. button.setAttribute('aria-label', 'Toggle GPT-4o Mini');
  101. button.innerHTML = `<svg width='24' height='24' viewBox='0 0 320 320' xmlns='http://www.w3.org/2000/svg'><path d='m297.06 130.97c7.26-21.79 4.76-45.66-6.85-65.48-17.46-30.4-52.56-46.04-86.84-38.68-15.25-17.18-37.16-26.95-60.13-26.81-35.04-.08-66.13 22.48-76.91 55.82-22.51 4.61-41.94 18.7-53.31 38.67-17.59 30.32-13.58 68.54 9.92 94.54-7.26 21.79-4.76 45.66 6.85 65.48 17.46 30.4 52.56 46.04 86.84 38.68 15.24 17.18 37.16 26.95 60.13 26.8 35.06.09 66.16-22.49 76.94-55.86 22.51-4.61 41.94-18.7 53.31-38.67 17.57-30.32 13.55-68.51-9.94-94.51zm-120.28 168.11c-14.03.02-27.62-4.89-38.39-13.88.49-.26 1.34-.73 1.89-1.07l63.72-36.8c3.26-1.85 5.26-5.32 5.24-9.07v-89.83l26.93 15.55c.29.14.48.42.52.74v74.39c-.04 33.08-26.83 59.9-59.91 59.97zm-128.84-55.03c-7.03-12.14-9.56-26.37-7.15-40.18.47.28 1.3.79 1.89 1.13l63.72 36.8c3.23 1.89 7.23 1.89 10.47 0l77.79-44.92v31.1c.02.32-.13.63-.38.83l-64.41 37.19c-28.69 16.52-65.33 6.7-81.92-21.95zm-16.77-139.09c7-12.16 18.05-21.46 31.21-26.29 0 .55-.03 1.52-.03 2.2v73.61c-.02 3.74 1.98 7.21 5.23 9.06l77.79 44.91-26.93 15.55c-.27.18-.61.21-.91.08l-64.42-37.22c-28.63-16.58-38.45-53.21-21.95-81.89zm221.26 51.49-77.79-44.92 26.93-15.54c.27-.18.61-.21.91-.08l64.42 37.19c28.68 16.57 38.51 53.26 21.94 81.94-7.01 12.14-18.05 21.44-31.2 26.28v-75.81c.03-3.74-1.96-7.2-5.2-9.06zm26.8-40.34c-.47-.29-1.3-.79-1.89-1.13l-63.72-36.8c-3.23-1.89-7.23-1.89-10.47 0l-77.79 44.92v-31.1c-.02-.32.13-.63.38-.83l64.41-37.16c28.69-16.55 65.37-6.7 81.91 22 6.99 12.12 9.52 26.31 7.15 40.1zm-168.51 55.43-26.94-15.55c-.29-.14-.48-.42-.52-.74v-74.39c.02-33.12 26.89-59.96 60.01-59.94 14.01 0 27.57 4.92 38.34 13.88-.49.26-1.33.73-1.89 1.07l-63.72 36.8c-3.26 1.85-5.26 5.31-5.24 9.06l-.04 89.79zm14.63-31.54 34.65-20.01 34.65 20v40.01l-34.65 20-34.65-20z'/></svg>`
  102. button.style.backgroundColor = this.useMini ? 'hsl(102, 58%, 39%)' : 'transparent';
  103. button.addEventListener('click', () => {
  104. this.useMini = !this.useMini;
  105. console.log( `useMini: ${this.useMini}` );
  106. GM.setValue('useMini', this.useMini);
  107. button.setAttribute('aria-pressed', this.useMini);
  108. button.title = `Using model: ${this.useMini ? 'GPT-4o mini' : 'original'}`;
  109. button.style.backgroundColor = this.useMini ? 'hsl(102, 58%, 39%)' : 'transparent';
  110. });
  111. container.appendChild(button);
  112. }
  113.  
  114. monitorChild( nodeSelector, callback ) {
  115. const node = document.querySelector( nodeSelector );
  116. if ( !node ) {
  117. console.log( `${ nodeSelector } not found!` )
  118. return;
  119. }
  120. const observer = new MutationObserver( mutationsList => {
  121. for ( const mutation of mutationsList ) {
  122. console.log( nodeSelector );
  123. callback( observer, mutation );
  124. break;
  125. }
  126. });
  127. observer.observe( node, { childList: true } );
  128. }
  129.  
  130. __tagAttributeRecursively(selector) {
  131. // Select the node using the provided selector
  132. const rootNode = document.querySelector(selector);
  133. if (!rootNode) {
  134. console.warn(`No element found for selector: ${selector}`);
  135. return;
  136. }
  137.  
  138. // Recursive function to add the "xx" attribute to the node and its children
  139. function addAttribute(node) {
  140. node.setAttribute("xxx", ""); // Add the attribute to the current node
  141. Array.from(node.children).forEach(addAttribute); // Recurse for all child nodes
  142. }
  143.  
  144. addAttribute(rootNode);
  145. }
  146.  
  147.  
  148. monitorNodesAndInject() {
  149. this.monitorChild( 'body main', () => {
  150. this.injectToggleButton();
  151.  
  152. this.monitorChild( 'main div:first-child div:first-child', ( observer, mutation ) => {
  153. observer.disconnect();
  154. this.injectToggleButton();
  155. });
  156. });
  157.  
  158. this.monitorChild( this.containerSelector, ( observer, mutation ) => {
  159. observer.disconnect();
  160. setTimeout( () => this.injectToggleButton(), 500 );
  161. });
  162. this.monitorChild( 'main div:first-child div:first-child', ( observer, mutation ) => {
  163. observer.disconnect();
  164. this.injectToggleButton();
  165. });
  166. }
  167. }
  168.  
  169. const useMini = await GM.getValue( 'useMini', true );
  170. const switcher = new ModelSwitcher( useMini );
  171. switcher.hookFetch();
  172. switcher.injectToggleButtonStyle();
  173. switcher.monitorNodesAndInject();
  174. })();