您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Injects a dropdown allowing you to switch LLMs during the chat
// ==UserScript== // @name ChatGPT Model Switcher: Switch models // @description Injects a dropdown allowing you to switch LLMs during the chat // @namespace http://github.com/c0des1ayr // @author c0des1ayr // @license MIT // @version 1.0.1 // @match *://chatgpt.com/* // @icon https://chatgpt.com/favicon.ico // @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(); })();