// ==UserScript== // @name ChatGPT Model Switcher: Toggle on/off 4o-mini // @namespace http://tampermonkey.net/ // @version 0.3 // @description Injects a button allowing you to toggle on/off 4o-mini during the chat // @match *://chatgpt.com/* // @author d0gkiller87 // @license MIT // @grant unsafeWindow // @grant GM.setValue // @grant GM.getValue // @run-at document-idle // @icon https://www.google.com/s2/favicons?sz=64&domain=chatgpt.com // @downloadURL none // ==/UserScript== (async function() { 'use strict'; class ModelSwitcher { constructor( useMini = true ) { this.useMini = useMini; this.containerSelector = 'div[style*=--vt-composer-]'; } hookFetch() { const originalFetch = unsafeWindow.fetch; unsafeWindow.fetch = async ( resource, config = {} ) => { if ( resource === 'https://chatgpt.com/backend-api/conversation' && config.method === 'POST' && config.headers && config.headers['Content-Type'] === 'application/json' && config.body ) { if ( this.useMini ) { const body = JSON.parse( config.body ); body.model = 'gpt-4o-mini'; config.body = JSON.stringify( body ); } } return originalFetch( resource, config ); }; } injectToggleButtonStyle() { // Credit: https://webdevworkshop.io/code/css-toggle-button/ if ( !document.getElementById( 'toggleCss' ) ) { const styleNode = document.createElement( 'style' ); styleNode.id = 'toggleCss'; styleNode.type = 'text/css'; styleNode.textContent = `.toggle { position: relative; display: inline-block; width: 2.5rem; height: 1.5rem; background-color: hsl(0deg 0% 40%); border-radius: 25px; cursor: pointer; transition: background-color 0.2s ease-in; } .toggle::after { content: ''; position: absolute; width: 1.4rem; left: 0.1rem; height: calc(1.5rem - 2px); top: 1px; background-color: white; border-radius: 50%; transition: all 0.2s ease-out; } #cb-toggle:checked + .toggle { background-color: hsl(102, 58%, 39%); } #cb-toggle:checked + .toggle::after { transform: translateX(1rem); } .hide-me { opacity: 0; height: 0; width: 0; display: none; }`; document.head.appendChild( styleNode ); } } getContainer() { return document.querySelector( this.containerSelector ).parentElement; } injectToggleButton( container = null ) { console.log( 'inject' ); if ( !container ) container = this.getContainer(); if ( !container ) { console.error( 'container not found!' ); return; } if ( container.querySelector( '#cb-toggle' ) ) { console.log( '#cb-toggle already exists' ); return; } container.classList.add( 'items-center' ); // const checkbox = document.createElement( 'input' ); checkbox.id = 'cb-toggle'; checkbox.type = 'checkbox'; checkbox.className = 'hide-me'; checkbox.checked = this.useMini; // const label = document.createElement( 'label' ); label.htmlFor = 'cb-toggle'; label.className = 'toggle'; label.title = `Using model: ${ this.useMini ? 'GPT-4o mini' : 'original' }`; container.appendChild( checkbox ); container.appendChild( label ); const cb = document.querySelector( '#cb-toggle' ); cb.addEventListener( 'click', async () => { this.useMini = cb.checked; await GM.setValue( 'useMini', this.useMini ); label.title = `Using model: ${ this.useMini ? 'GPT-4o mini' : 'original' }`; }, false ); } monitorChild( nodeSelector, is_parent, callback ) { let node = document.querySelector( nodeSelector ); if ( is_parent ) node = node.parentElement; if ( !node ) { console.log( `${ nodeSelector } not found!` ) return; } const observer = new MutationObserver( mutationsList => { for ( const mutation of mutationsList ) { console.log( nodeSelector ); callback( observer, mutation ); break; } }); observer.observe( node, { childList: true } ); } __tagAttributeRecursively( selector ) { // Select the node using the provided selector const rootNode = document.querySelector( selector ); if ( !rootNode ) { console.warn( `No element found for selector: ${ selector }` ); return; } // Recursive function to add the "xx" attribute to the node and its children function addAttribute( node ) { node.setAttribute( "xxx", "" ); // Add the attribute to the current node Array.from( node.children ).forEach( addAttribute ); // Recurse for all child nodes } addAttribute( rootNode ); } monitorNodesAndInject() { this.monitorChild( 'body main' /* switching conversation */, false, () => { this.injectToggleButton(); this.monitorChild( '.composer-parent > div:nth-child(2)' /* new conversation */, false, ( observer, mutation ) => { observer.disconnect(); this.injectToggleButton(); }); }); this.monitorChild( this.containerSelector /* init */, true, ( observer, mutation ) => { observer.disconnect(); setTimeout( () => this.injectToggleButton(), 500 ); }); this.monitorChild( '.composer-parent > div:nth-child(2)' /* new conversation */, false, ( observer, mutation ) => { observer.disconnect(); this.injectToggleButton(); }); } } const useMini = await GM.getValue( 'useMini', true ); const switcher = new ModelSwitcher( useMini ); switcher.hookFetch(); switcher.injectToggleButtonStyle(); switcher.monitorNodesAndInject(); })();