您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Enhancements for ChatGPT
- // ==UserScript==
- // @name ChatGPT Zero
- // @namespace https://github.com/NextDev65/
- // @version 0.5
- // @description Enhancements for ChatGPT
- // @author NextDev65
- // @homepageURL https://github.com/NextDev65/ChatGPT-0
- // @supportURL https://github.com/NextDev65/ChatGPT-0
- // @match https://chatgpt.com/*
- // @grant none
- // ==/UserScript==
- (function () {
- 'use strict';
- // --- Configuration ---
- const PREFERRED_MODEL_KEY = 'preferredChatGPTModel';
- const SETTINGS_KEY = 'chatgptZeroSettings';
- const DEFAULT_MODEL = 'gpt-4o-mini';
- const MODELS = [
- 'gpt-3.5-turbo',
- 'text-davinci-002-render-sha',
- 'text-davinci-002-render-sha-mobile',
- 'gpt-4-mobile',
- 'gpt-4o-mini',
- 'gpt-4-1-mini',
- 'gpt-4o',
- 'o4-mini'
- ];
- // Default settings
- const DEFAULT_SETTINGS = {
- modelSwitcher: true,
- streamerMode: true,
- animations: true
- };
- // Load settings from localStorage
- function loadSettings() {
- try {
- const saved = localStorage.getItem(SETTINGS_KEY);
- return saved ? { ...DEFAULT_SETTINGS, ...JSON.parse(saved) } : { ...DEFAULT_SETTINGS };
- } catch (e) {
- console.warn('Failed to load settings, using defaults', e);
- return { ...DEFAULT_SETTINGS };
- }
- }
- // Save settings to localStorage
- function saveSettings(settings) {
- try {
- localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
- } catch (e) {
- console.warn('Failed to save settings', e);
- }
- }
- // Global settings object
- let settings = loadSettings();
- /**
- * Creates a toggle switch element
- * @param {string} label - The label text for the toggle
- * @param {boolean} checked - Initial checked state
- * @param {Function} onChange - Callback when toggle changes
- * @returns {HTMLDivElement}
- */
- function createToggleSwitch(label, checked, onChange) {
- const container = document.createElement('div');
- container.className = 'toggle-container';
- const labelElement = document.createElement('label');
- labelElement.className = 'toggle-label';
- labelElement.textContent = label;
- const switchContainer = document.createElement('label');
- switchContainer.className = 'toggle-switch';
- const input = document.createElement('input');
- input.type = 'checkbox';
- input.checked = checked;
- input.className = 'toggle-input';
- input.addEventListener('change', onChange);
- const slider = document.createElement('span');
- slider.className = 'toggle-slider';
- switchContainer.appendChild(input);
- switchContainer.appendChild(slider);
- container.appendChild(labelElement);
- container.appendChild(switchContainer);
- return container;
- }
- /**
- * Creates and returns a <button> element with an attached settings menu.
- * @returns {HTMLButtonElement}
- */
- function createSettingsMenu() {
- const cog = document.createElement('button');
- cog.id = 'settings-cog';
- //cog.textContent = settings.animations ? '⚙️' : '⚙';
- cog.setAttribute('aria-label', 'Settings');
- const menu = document.createElement('div');
- menu.id = 'settings-menu';
- menu.className = 'settings-dropdown';
- menu.style.display = 'none';
- // Create toggle switches
- const modelSwitcherToggle = createToggleSwitch('Model Switcher', settings.modelSwitcher, (e) => {
- settings.modelSwitcher = e.target.checked;
- saveSettings(settings);
- updateModelSwitcherVisibility();
- });
- const streamerModeToggle = createToggleSwitch(
- 'Streamer Mode',
- settings.streamerMode ?? true,
- (e) => {
- settings.streamerMode = e.target.checked;
- saveSettings(settings);
- updateStreamerModeStyles();
- }
- );
- const animationsToggle = createToggleSwitch('Animations', settings.animations, (e) => {
- settings.animations = e.target.checked;
- saveSettings(settings);
- updateAnimationStyles();
- });
- menu.appendChild(modelSwitcherToggle);
- menu.appendChild(streamerModeToggle);
- menu.appendChild(animationsToggle);
- // Append menu to body to avoid positioning issues
- document.body.appendChild(menu);
- // Toggle menu visibility
- cog.addEventListener('click', (e) => {
- e.stopPropagation();
- //const isVisible = window.getComputedStyle(menu).display !== 'none';
- if (menu.style.display === 'block')
- {
- menu.style.display = 'none';
- }
- else {
- positionMenu();
- menu.style.display = 'block';
- }
- });
- // Close menu when clicking outside
- document.addEventListener('click', (e) => {
- if (!cog.contains(e.target) && !menu.contains(e.target)) {
- menu.style.display = 'none';
- }
- });
- // Position menu relative to cog
- function positionMenu() {
- // cog bounds, changes when cog is rotated (animations enabled) -> alignment inconsistencies
- const cogRect = cog.getBoundingClientRect();
- // page header bounds
- const parentRect = cog.parentElement.getBoundingClientRect();
- const viewportWidth = window.innerWidth;
- menu.style.position = 'fixed';
- menu.style.top = `${parentRect.bottom - 5}px`; // 5px above `page-header`
- menu.style.zIndex = '10000';
- const cogRight = cogRect.left + cogRect.width;
- const rightOffset = viewportWidth - cogRight;
- // prepare initial state
- menu.style.right = `${rightOffset}px`;
- menu.style.left = 'auto';
- if (settings.animations) {
- menu.style.opacity = '0';
- menu.style.transform = 'translateX(10px)';
- menu.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
- /*// force a reflow so the browser registers the start state
- // eslint-disable-next-line @microsoft/sdl/no-document-domain -- reflow hack
- void menu.offsetWidth;*/
- // slide into place
- requestAnimationFrame(() => {
- menu.style.opacity = '1';
- menu.style.transform = 'translateX(0)';
- });
- }
- }
- // Inject CSS for settings menu and toggle switches
- injectSettingsStyles();
- return cog;
- }
- /**
- * Injects CSS styles for the settings menu and components
- */
- function injectSettingsStyles() {
- if (document.getElementById('settings-styles')) return;
- const style = document.createElement('style');
- style.id = 'settings-styles';
- style.textContent = `
- #settings-cog {
- font-size: 20px;
- margin-left: 12px;
- padding: 4px 5px;
- border: none;
- border-radius: 50%;
- background-color: #212121;
- color: #fff;
- cursor: pointer;
- box-shadow: 0 0 0 0 rgba(33, 33, 33, 0) inset,
- 0 0 5px 0 rgba(33, 33, 33, 0);
- display: flex;
- align-items: center;
- justify-content: center;
- position: relative;
- transform: translateX(0.75px) translateY(-0.75px);
- ${settings.animations ? `
- transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1),
- box-shadow 0.4s cubic-bezier(0.4, 0, 0.2, 1);
- ` : ''}
- ${settings.animations ? `
- transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55);
- ` : ''}
- }
- #settings-cog:hover {
- background-color: #2f2f2f;
- box-shadow: 0 0 2.5px 0 rgba(255, 255, 255, 0) inset,
- 0 0 5px 0 rgba(255, 255, 255, 0.2);
- ${settings.animations ? `
- transform: translateX(0.75px) translateY(-0.75px) rotate(45deg);
- ` : `
- transform: translateX(0.75px) translateY(-0.75px);
- `}
- }
- #settings-cog:focus {
- outline: none;
- box-shadow: 0 0 2.5px 0 rgba(255, 255, 255, 0.5) inset,
- 0 0 5px 0 rgba(255, 255, 255, 0.5);
- }
- #settings-cog::before {
- content: '${settings.animations ? '⚙️' : '⚙'}';
- transform-origin: center;
- transform: translateX(0.75px) translateY(-0.75px);
- }
- .settings-dropdown {
- display: none;
- background-color: #2a2a2a;
- border: 1px solid #444;
- border-radius: 8px;
- padding: 12px;
- min-width: 200px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
- }
- .toggle-container {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 12px;
- }
- .toggle-container:last-child {
- margin-bottom: 0;
- }
- .toggle-label {
- color: #fff;
- font-size: 14px;
- }
- .toggle-switch {
- position: relative;
- display: inline-block;
- width: 44px;
- height: 24px;
- }
- .toggle-input {
- position: absolute;
- opacity: 0;
- width: 100%;
- height: 100%;
- cursor: pointer;
- z-index: 1;
- }
- .toggle-input:checked + .toggle-slider {
- background-color: #4CAF50;
- }
- .toggle-input:checked + .toggle-slider:before {
- transform: translateX(20px);
- }
- .toggle-input:checked + .toggle-slider:hover {
- background-color: #45a049;
- }
- .toggle-slider {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: #555;
- border-radius: 24px;
- ${settings.animations ? `
- transition: background-color 0.3s cubic-bezier(0.68, -0.1, 0.27, 1.1),
- transform 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55);
- ` : ''}
- }
- .toggle-slider:before {
- content: "";
- position: absolute;
- height: 18px;
- width: 18px;
- left: 3px;
- bottom: 3px;
- background-color: white;
- border-radius: 50%;
- ${settings.animations ? `
- transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55);
- ` : ''}
- }
- `;
- document.head.appendChild(style);
- }
- /**
- * Updates animation styles based on current settings
- */
- function updateAnimationStyles() {
- // Remove existing styles and re-inject with updated animation settings
- document.getElementById('settings-styles')?.remove();
- document.getElementById('model-switcher-styles')?.remove();
- injectSettingsStyles();
- // Re-inject model switcher styles if it exists
- const modelSwitcher = document.getElementById('chatgpt-model-switcher');
- if (modelSwitcher) {
- injectModelSwitcherStyles();
- }
- if (settings.streamerMode) {
- updateStreamerModeStyles();
- }
- }
- function updateStreamerModeStyles() {
- injectStreamerModeStyles();
- }
- function injectStreamerModeStyles() {
- // Remove old rules
- document.getElementById('streamer-styles')?.remove();
- if (!settings.streamerMode) return; // nothing to do if disabled
- const style = document.createElement('style');
- style.id = 'streamer-styles';
- style.textContent = `
- /* inactive chats */
- #history .__menu-item:not([data-active]) {
- box-shadow: 0 0 2.5px 0 rgba(255, 255, 255, 0) inset,
- 0 0 5px 0 rgba(255, 255, 255, 0.2);
- ${settings.animations ? `
- transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1),
- box-shadow 0.4s cubic-bezier(0.4, 0, 0.2, 1);
- ` : ''}
- }
- /* inactive chat titles */
- #history .__menu-item:not([data-active]) .truncate span {
- opacity: 0;
- ${settings.animations ? `
- transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1),
- box-shadow 0.4s cubic-bezier(0.4, 0, 0.2, 1);
- ` : ''}
- }
- #history .__menu-item:not([data-active]):hover .truncate span {
- opacity: 1;
- }
- `;
- document.head.appendChild(style);
- }
- /**
- * Updates model switcher visibility based on settings
- */
- function updateModelSwitcherVisibility() {
- const modelSwitcher = document.getElementById('chatgpt-model-switcher');
- if (modelSwitcher) {
- modelSwitcher.style.display = settings.modelSwitcher ? 'block' : 'none';
- }
- }
- /**
- * Injects CSS styles for the model switcher
- */
- function injectModelSwitcherStyles() {
- if (document.getElementById('model-switcher-styles')) return;
- const style = document.createElement('style');
- style.id = 'model-switcher-styles';
- style.textContent = `
- #chatgpt-model-switcher {
- margin: auto;
- padding: 4px 8px;
- border: none;
- border-radius: 6px;
- background-color: #212121;
- color: #fff;
- outline: none;
- ${settings.animations ? `
- box-shadow: 0 0 0 0 rgba(33, 33, 33, 0) inset,
- 0 0 5px 0 rgba(33, 33, 33, 0);
- transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1),
- box-shadow 0.4s cubic-bezier(0.4, 0, 0.2, 1);
- ` : ''}
- }
- #chatgpt-model-switcher:hover {
- background-color: #2f2f2f;
- box-shadow: 0 0 2.5px 0 rgba(255, 255, 255, 0) inset,
- 0 0 5px 0 rgba(255, 255, 255, 0.2);
- }
- #chatgpt-model-switcher:focus {
- outline: none;
- box-shadow: 0 0 2.5px 0 rgba(255, 255, 255, 0.5) inset,
- 0 0 5px 0 rgba(255, 255, 255, 0.5);
- }
- `;
- document.head.appendChild(style);
- }
- /**
- * Creates and returns a <select> element configured as the model switcher.
- * @param {string} currentModel - Model to pre-select in the dropdown.
- * @returns {HTMLSelectElement}
- */
- function createModelSwitcher(currentModel) {
- const select = document.createElement('select');
- select.id = 'chatgpt-model-switcher';
- // Inject CSS for base styling, hover, focus, and transition effects
- injectModelSwitcherStyles();
- // Populate dropdown with model options
- MODELS.forEach(model => {
- const option = document.createElement('option');
- option.value = model;
- option.textContent = model;
- if (model === currentModel) option.selected = true;
- select.appendChild(option);
- });
- // Save selection to localStorage on change
- select.addEventListener('change', () => {
- localStorage.setItem(PREFERRED_MODEL_KEY, select.value);
- });
- // Set initial visibility based on settings
- select.style.display = settings.modelSwitcher ? 'block' : 'none';
- return select;
- }
- /**
- * Finds our model switcher in the UI and inserts the settings cog after it.
- * Retries every second until our model switcher is visible.
- */
- function injectSettingsMenu() {
- const checkInterval = setInterval(() => {
- const modelSwitcher = document.getElementById('chatgpt-model-switcher');
- let cog = document.getElementById('settings-cog');
- // Create cog if it doesn't exist yet
- if (!cog) {
- cog = createSettingsMenu();
- }
- // Insert cog after visible model switcher
- if (modelSwitcher && !cog.parentNode && modelSwitcher.parentNode) {
- modelSwitcher.parentNode.insertBefore(cog, modelSwitcher.nextSibling);
- }
- }, 1000);
- }
- /**
- * Finds the native model switcher in the UI and inserts our custom switcher beside it.
- * Retries every second until the native element is visible.
- */
- function injectModelSwitcher() {
- const checkInterval = setInterval(() => {
- const nativeModelSwitchers = document.querySelectorAll('[data-testid="model-switcher-dropdown-button"]');
- let switcher = document.getElementById('chatgpt-model-switcher');
- // Create switcher
- if (!switcher) {
- const savedModel = localStorage.getItem(PREFERRED_MODEL_KEY) || DEFAULT_MODEL;
- switcher = createModelSwitcher(savedModel);
- }
- // Insert switcher next to the first visible native button
- if (!switcher.parentNode) {
- for (let nativeModelSwitcher of nativeModelSwitchers) {
- if (nativeModelSwitcher.checkVisibility && nativeModelSwitcher.checkVisibility()) {
- nativeModelSwitcher.parentNode.after(switcher);
- break;
- }
- }
- }
- }, 1000);
- }
- /**
- * Overrides window.fetch to intercept conversation requests and replace the model
- * property in the request body with the user-selected model.
- */
- function overrideModelInRequest() {
- // Only override if model switcher is enabled
- if (!settings.modelSwitcher) return;
- const origFetch = window.fetch;
- window.fetch = async (...args) => {
- const [resource, config] = args;
- const savedModel = localStorage.getItem(PREFERRED_MODEL_KEY) || DEFAULT_MODEL;
- // Target only conversation API calls
- if (
- typeof resource === 'string' &&
- resource.includes('/backend-api/f/conversation') &&
- config?.body
- ) {
- try {
- const body = JSON.parse(config.body);
- if (body && body.model) {
- // Overwrite model
- body.model = savedModel;
- config.body = JSON.stringify(body);
- }
- } catch (e) {
- console.warn('Model switcher failed to parse request body', e);
- }
- }
- return origFetch(resource, config);
- };
- }
- // Initialize the userscript
- injectModelSwitcher();
- overrideModelInRequest();
- injectSettingsMenu();
- updateStreamerModeStyles();
- })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址