ChatGPT Model Switcher: Switch models

Injects a dropdown allowing you to switch LLMs during the chat

目前為 2025-03-24 提交的版本,檢視 最新版本

// ==UserScript==
// @name         ChatGPT Model Switcher: Switch models
// @namespace    http://github.com/c0des1ayr
// @version      1.0.0
// @description  Injects a dropdown allowing you to switch LLMs during the chat
// @match        *://chatgpt.com/*
// @author       c0des1ayr
// @license      MIT
// @grant        unsafeWindow
// @grant        GM.setValue
// @grant        GM.getValue
// @grant        GM.xmlHttpRequest
// @connect      api.github.com
// @run-at       document-idle
// ==/UserScript==

(async function() {
  'use strict';
  /**
  * Fetches a JSON file from a GitHub pre-release.
  * @returns {Promise<Object>} - A promise that resolves to the JSON list of models.
  */
  function getModels() {
    return new Promise((resolve, reject) => {
      const releasesUrl = `https://api.github.com/repos/c0des1ayr/openai-models-list/releases`;

      GM.xmlHttpRequest({
        method: 'GET',
        url: releasesUrl,
        onload: function(response) {
          if (response.status === 200) {
            const releases = JSON.parse(response.responseText);
            const release = releases.find(r => r.tag_name === "continuous" && r.prerelease);
            if (release) {
              const asset = release.assets.find(a => a.name === "models.json");
              if (asset) {
                GM.xmlHttpRequest({
                  method: 'GET',
                  url: asset.browser_download_url,
                  onload: function(assetResponse) {
                    if (assetResponse.status === 200) {
                      try {
                        const data = JSON.parse(assetResponse.responseText);
                        resolve(data);
                      } catch (e) {
                        reject('Error parsing JSON data: ' + e);
                      }
                    } else {
                      reject('Failed to download asset: ' + assetResponse.status);
                    }
                  },
                  onerror: function() {
                    reject('Error downloading asset');
                  }
                });
              } else {
                reject('Asset not found in the release');
              }
            } else {
              reject('Release not found');
            }
          } else {
            reject('Failed to fetch releases: ' + response.status);
          }
        },
        onerror: function() {
          reject('Error fetching releases');
        }
      });
    });
  }

  class ModelSwitcher {
    constructor( useOther = "Original", models ) {
      this.useOther = useOther;
      this.models = models;
      this.containerSelector = '#composer-background div:nth-of-type(2) div:first-child';
    }

    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.useOther != "Original" ) {
            const body = JSON.parse( config.body );
            console.log('hehe, using', this.useOther)
            body.model = this.useOther;
            config.body = JSON.stringify( body );
          }
        }
        return originalFetch( resource, config );
      };
    }

    injectDropdownStyle() {
      if ( !document.getElementById( 'dropdownCss' ) ) {
        const styleNode = document.createElement( 'style' );
        styleNode.id = 'dropdownCss';
        styleNode.type = 'text/css';
        styleNode.textContent = `.dropdown {
  position: relative;
  display: inline-block;
  width: 2.5rem;
  height: 1.5rem;
  background-color: hsl(0deg 0% 40%);
  border-radius: 25px;
  background-color: var(--dropdown-bg-color, #ccc);
  color: var(--dropdown-text-color, #000);
  border: 1px solid #666;
  padding: 0.2rem;
}
@media (prefers-color-scheme: dark) {
  .dropdown {
    --dropdown-bg-color: #333;
    --dropdown-text-color: #fff;
  }
}
@media (prefers-color-scheme: light) {
  .dropdown {
    --dropdown-bg-color: #fff;
    --dropdown-text-color: #000;
  }
}`;
        document.head.appendChild( styleNode );
      }
    }

    getContainer() {
      return document.querySelector( this.containerSelector );
    }

    injectDropdown( container = null ) {
      console.log( 'inject' );
      if ( !container ) container = this.getContainer();
      if ( !container ) {
        console.error( 'container not found!' );
        return;
      }
      if ( container.querySelector( '#cb-dropdown' ) ) {
        console.log( '#cb-dropdown already exists' );
        return;
      }
      container.classList.add( 'items-center' );

      const dropdown = document.createElement( 'select' );
      dropdown.id = 'cb-dropdown';
      dropdown.className = 'dropdown'

      const original = document.createElement( 'option' );
      original.value = 0;
      original.text = "Original"

      container.appendChild( dropdown );
      dropdown.options.add( original );
      for (let i = 0; i < this.models.length; i++) {
        let option = document.createElement( 'option' );
        option.value = i + 1;
        option.text = models[i];
        dropdown.options.add( option );
      }

      for (let i = 0; i < dropdown.options.length; i++){
        if (dropdown.options[i].value == this.useOther){
          dropdown.options[i].selected = true;
          break;
        }
      }

      const cb = document.querySelector( '#cb-dropdown' );
      cb.addEventListener(
        'change', async () => {
          this.useOther = cb.options[cb.selectedIndex].text;
          console.log('Using', this.useOther)
          await GM.setValue( 'useOther', this.useOther );
        },
        false
      );
    }

    monitorChild( nodeSelector, callback ) {
      const node = document.querySelector( nodeSelector );
      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', () => {
        this.injectDropdown();

        this.monitorChild( 'main div:first-child div:first-child', ( observer, mutation ) => {
          observer.disconnect();
          this.injectDropdown();
        });
      });

      this.monitorChild( this.containerSelector, ( observer, mutation ) => {
        observer.disconnect();
        setTimeout( () => this.injectDropdown(), 500 );
      });
      this.monitorChild( 'main div:first-child div:first-child', ( observer, mutation ) => {
        observer.disconnect();
        this.injectDropdown();
      });
    }
  }

  const useOther = await GM.getValue( 'useOther', "Original" );
  const models = await (async () => {
    do {
      try {
        const oai = await getModels();
        return oai.models;
      } catch (error) {
        console.error('Error fetching data:', error);
      }
    } while (true);
  })();
  if (models === false) {return}
  const switcher = new ModelSwitcher( useOther, models );
  switcher.hookFetch();
  switcher.injectDropdownStyle();
  switcher.monitorNodesAndInject();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址