Greasy Fork

Greasy Fork is available in English.

Switch Bug Team Model

Bug Team —— 好用、爱用 ♥

当前为 2025-04-20 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Switch Bug Team Model
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Bug Team —— 好用、爱用 ♥
// @author       wandouyu
// @match        *://chatgpt.com/*
// @match        *://chat.openai.com/*
// @match        *://chat.voct.dev/*
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const modelMap = {
        "o3 ": "o3",
        "o4-mini-high": "o4-mini-high",
        "o4-mini": "o4-mini",
        "gpt-4.5 (preview)": "gpt-4-5",
        "gpt-4o": "gpt-4o",
        "gpt-4o-mini": "gpt-4o-mini",
        "gpt-4o (tasks)": "gpt-4o-jawbone",
        "gpt-4": "gpt-4"
    };
    const modelDisplayNames = Object.keys(modelMap);
    const modelIds = Object.values(modelMap);

    let dropdownElement = null;
    let isDropdownVisible = false;

    GM_addStyle(`
        .model-switcher-container {
            position: relative;
            display: inline-block;
            margin-left: 8px; 
        }

        #model-switcher-button {
            
            display: inline-flex;
            align-items: center;
            justify-content: center;
            height: 36px;
            min-width: 36px;
            padding: 0 12px;
            border-radius: 9999px;
            border: 1px solid var(--token-border-light, #E5E5E5);
            font-size: 14px;
            font-weight: 500;
            color: var(--token-text-secondary, #666666);
            background-color: var(--token-main-surface-primary, #FFFFFF);
            cursor: pointer;
            white-space: nowrap;
            transition: background-color 0.2s ease;
            box-sizing: border-box;
            
        }

        #model-switcher-button:hover {
            background-color: var(--token-main-surface-secondary, #F7F7F8);
        }

        
        #model-switcher-dropdown {
            
            position: fixed; 
            display: block; 
            background-color: var(--token-main-surface-primary, white);
            border: 1px solid var(--token-border-medium, #E5E5E5);
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
            z-index: 1050; 
            min-width: 180px;
            max-height: 300px;
            overflow-y: auto;
            padding: 4px 0;
            
        }

        .model-switcher-item {
            display: block;
            padding: 8px 16px;
            color: var(--token-text-primary, #171717);
            text-decoration: none;
            white-space: nowrap;
            cursor: pointer;
            font-size: 14px;
        }

        .model-switcher-item:hover {
            background-color: var(--token-main-surface-secondary, #F7F7F8);
        }

        .model-switcher-item.active {
            font-weight: bold;
        }
    `);

    function getCurrentModelInfo() {
        const params = new URLSearchParams(window.location.search);
        const currentModelId = params.get('model');
        let currentDisplayName = "Select Model"; 
        let currentIndex = -1; 

        if (currentModelId) {
            const index = modelIds.indexOf(currentModelId);
            if (index !== -1) {
                currentIndex = index;
                currentDisplayName = modelDisplayNames[index];
            } else {
                
                currentDisplayName = `Model: ${currentModelId.substring(0, 10)}${currentModelId.length > 10 ? '...' : ''}`;
                currentIndex = -1; 
            }
        } else {
             
             
             if (modelDisplayNames.length > 0) {
                 currentDisplayName = modelDisplayNames[0];
                 currentIndex = 0; 
             }
        }
        return { currentId: currentModelId, displayName: currentDisplayName, index: currentIndex };
    }

    function createModelSwitcher() {
        if (modelDisplayNames.length === 0) {
            console.warn("Model Switcher: modelMap is empty. Cannot create switcher.");
            return null;
        }

        
        const container = document.createElement('div');
        container.className = 'model-switcher-container';
        container.id = 'model-switcher-container'; 

        const button = document.createElement('button');
        button.id = 'model-switcher-button';
        button.type = 'button';

        
        const dropdown = document.createElement('div');
        dropdown.className = 'model-switcher-dropdown';
        dropdown.id = 'model-switcher-dropdown'; 

        
        const currentInfo = getCurrentModelInfo();
        button.textContent = currentInfo.displayName; 

        modelDisplayNames.forEach((name, index) => {
            const modelId = modelIds[index];
            const item = document.createElement('a');
            item.className = 'model-switcher-item';
            item.textContent = name;
            item.dataset.modelId = modelId;
            item.href = '#'; 

            
            
            if ((currentInfo.currentId && currentInfo.currentId === modelId) || (!currentInfo.currentId && index === 0)) {
                 item.classList.add('active');
            }

            item.addEventListener('click', (e) => {
                e.preventDefault();
                e.stopPropagation();
                const selectedModelId = e.target.dataset.modelId;
                if (selectedModelId) {
                    const url = new URL(window.location.href);
                    url.searchParams.set('model', selectedModelId);
                    window.location.href = url.toString(); 
                }
                hideDropdown(); 
            });
            dropdown.appendChild(item);
        });

        button.addEventListener('click', (e) => {
            e.stopPropagation(); 
            toggleDropdown();
        });

        container.appendChild(button);
        dropdownElement = dropdown; 

        return container; 
    }

    function showDropdown() {
        if (!dropdownElement || isDropdownVisible) return;

        const button = document.getElementById('model-switcher-button');
        if (!button) return;

        const buttonRect = button.getBoundingClientRect();
        const scrollX = window.scrollX || window.pageXOffset;
        const scrollY = window.scrollY || window.pageYOffset;

        
        document.body.appendChild(dropdownElement);
        isDropdownVisible = true; 

        
        const dropdownHeight = dropdownElement.offsetHeight;
        const spaceAbove = buttonRect.top;
        const spaceBelow = window.innerHeight - buttonRect.bottom;
        const margin = 5; 

        let top, left = buttonRect.left + scrollX; 

        
        if (spaceAbove > dropdownHeight + margin || spaceAbove >= spaceBelow) {
            top = buttonRect.top + scrollY - dropdownHeight - margin;
        } else {
            top = buttonRect.bottom + scrollY + margin;
        }

        
        if (top < scrollY + margin) top = scrollY + margin; 
        if (left < scrollX + margin) left = scrollX + margin; 
        
        const dropdownWidth = dropdownElement.offsetWidth;
        if (left + dropdownWidth > window.innerWidth + scrollX - margin) {
            left = window.innerWidth + scrollX - dropdownWidth - margin;
        }


        dropdownElement.style.top = `${top}px`;
        dropdownElement.style.left = `${left}px`;

        
        
        document.addEventListener('click', handleClickOutside, true);
        window.addEventListener('resize', hideDropdown);
        window.addEventListener('scroll', hideDropdown, true);

    }

    function hideDropdown() {
        if (!dropdownElement || !isDropdownVisible) return;

        
        if (dropdownElement.parentNode === document.body) {
            document.body.removeChild(dropdownElement);
        }
        isDropdownVisible = false;

        
        document.removeEventListener('click', handleClickOutside, true);
        window.removeEventListener('resize', hideDropdown);
        window.removeEventListener('scroll', hideDropdown, true);

    }

    function toggleDropdown() {
        if (isDropdownVisible) {
            hideDropdown();
        } else {
            showDropdown();
        }
    }

    
    function handleClickOutside(event) {
        const button = document.getElementById('model-switcher-button');
        
        if (dropdownElement && dropdownElement.parentNode === document.body && button && !button.contains(event.target) && !dropdownElement.contains(event.target)) {
             hideDropdown();
        }
    }


    
    function findCommentNode(parentElement, commentText) {
        const iterator = document.createNodeIterator(parentElement, NodeFilter.SHOW_COMMENT);
        let currentNode;
        while (currentNode = iterator.nextNode()) {
            if (currentNode.nodeValue.trim() === commentText) {
                return currentNode;
            }
        }
        return null;
    }

    
    function insertSwitcherButton() {
        const existingContainer = document.getElementById('model-switcher-container');

        
        if (existingContainer) {
            const button = document.getElementById('model-switcher-button');
            const currentInfo = getCurrentModelInfo();
            
            if(button && button.textContent !== currentInfo.displayName) {
                button.textContent = currentInfo.displayName;

                
                if (dropdownElement) {
                    const items = dropdownElement.querySelectorAll('.model-switcher-item');
                    items.forEach((item, index) => {
                        item.classList.remove('active');
                        const modelId = item.dataset.modelId;
                        
                        if ((currentInfo.currentId && currentInfo.currentId === modelId) || (!currentInfo.currentId && index === 0)) {
                            item.classList.add('active');
                        }
                    });
                }
            }
            return true; 
        }

        
        const switcherContainer = createModelSwitcher(); 
        if (!switcherContainer) return false; 

        
        
        const toolbar = document.querySelector('.max-xs\\:gap-1.flex.items-center.gap-2.overflow-x-auto');
        if (toolbar) {
            const commentNode = findCommentNode(toolbar, 'Insert code here');
            if (commentNode && commentNode.parentNode) {
                commentNode.parentNode.insertBefore(switcherContainer, commentNode);
                console.log('Model Switcher: Button inserted before comment.');
                return true;
            }
        }

        
        
        
        const toolsButton = document.querySelector('button[aria-label="Use a tool"]');
        const toolsButtonWrapper = toolsButton?.closest('div[class*="relative"]'); 
        if (toolsButtonWrapper && toolsButtonWrapper.parentNode && toolbar && toolbar.contains(toolsButtonWrapper)) {
            toolsButtonWrapper.parentNode.insertBefore(switcherContainer, toolsButtonWrapper);
            console.warn('Model Switcher: Comment not found. Inserted button before potential Tools button container.');
            return true;
        }

        
        if (toolbar) {
            toolbar.appendChild(switcherContainer);
            console.warn('Model Switcher: Comment and specific Tools container not found. Appended button to toolbar.');
            return true;
        }

        
        
        const composerArea = document.querySelector('textarea[tabindex="0"]')?.parentNode?.parentNode; 
         if (composerArea) {
             
             
             
             console.warn('Model Switcher: Toolbar not found. Attempting insertion near composer (may fail).');
             
             
         }


        console.error('Model Switcher: Could not find a suitable insertion point for the button.');
        return false; 
    }

    

    let insertionAttempted = false;
    const observer = new MutationObserver((mutationsList, obs) => {
        
        const targetParentExists = document.querySelector('.max-xs\\:gap-1.flex.items-center.gap-2.overflow-x-auto') || 
                                   document.querySelector('button[aria-label="Use a tool"]')?.closest('div'); 

        
        if (targetParentExists) {
            
            if (!document.getElementById('model-switcher-container')) {
                 if (insertSwitcherButton()) {
                     insertionAttempted = true; 
                     console.log("Model Switcher: Button check/insertion successful.");
                 } else if (!insertionAttempted) {
                     
                     console.error('Model Switcher: Found toolbar area, but failed to insert button container.');
                     insertionAttempted = true; 
                 }
            } else {
                
                insertSwitcherButton(); 
                insertionAttempted = true; 
            }
        }

        
        if (insertionAttempted && !document.getElementById('model-switcher-container')) {
             console.log("Model Switcher: Button container removed by UI update, attempting re-insertion...");
             insertionAttempted = false; 
             hideDropdown(); 
             
             setTimeout(insertSwitcherButton, 200);
        }
    });

    
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    
    setTimeout(insertSwitcherButton, 1500); 

})();