- // ==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);
-
- })();