ChatGPT Zero

Enhancements for ChatGPT

  1. // ==UserScript==
  2. // @name ChatGPT Zero
  3. // @namespace https://github.com/NextDev65/
  4. // @version 0.54
  5. // @description Enhancements for ChatGPT
  6. // @author NextDev65
  7. // @homepageURL https://github.com/NextDev65/ChatGPT-0
  8. // @supportURL https://github.com/NextDev65/ChatGPT-0
  9. // @match https://chatgpt.com/*
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15.  
  16. // --- Configuration ---
  17. const PREFERRED_MODEL_KEY = 'preferredChatGPTModel';
  18. const SETTINGS_KEY = 'chatgptZeroSettings';
  19. const DEFAULT_MODEL = 'gpt-4o-mini';
  20. const MODELS = [
  21. 'gpt-3.5-turbo',
  22. 'text-davinci-002-render-sha',
  23. 'text-davinci-002-render-sha-mobile',
  24. 'gpt-4-mobile',
  25. 'gpt-4o-mini',
  26. 'gpt-4-1-mini',
  27. 'gpt-4o',
  28. 'o3-mini',
  29. 'o4-mini'
  30. ];
  31.  
  32. // Default settings
  33. const DEFAULT_SETTINGS = {
  34. modelSwitcher: true,
  35. streamerMode: true,
  36. animations: true
  37. };
  38.  
  39. // Load settings from localStorage
  40. function loadSettings() {
  41. try {
  42. const saved = localStorage.getItem(SETTINGS_KEY);
  43. return saved ? { ...DEFAULT_SETTINGS, ...JSON.parse(saved) } : { ...DEFAULT_SETTINGS };
  44. } catch (e) {
  45. console.warn('Failed to load settings, using defaults', e);
  46. return { ...DEFAULT_SETTINGS };
  47. }
  48. }
  49.  
  50. // Save settings to localStorage
  51. function saveSettings(settings) {
  52. try {
  53. localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
  54. } catch (e) {
  55. console.warn('Failed to save settings', e);
  56. }
  57. }
  58.  
  59. // Global settings object
  60. let settings = loadSettings();
  61.  
  62. /**
  63. * Creates a toggle switch element
  64. * @param {string} label - The label text for the toggle
  65. * @param {boolean} checked - Initial checked state
  66. * @param {Function} onChange - Callback when toggle changes
  67. * @returns {HTMLDivElement}
  68. */
  69. function createToggleSwitch(label, checked, onChange) {
  70. const container = document.createElement('div');
  71. container.className = 'toggle-container';
  72.  
  73. const labelElement = document.createElement('label');
  74. labelElement.className = 'toggle-label';
  75. labelElement.textContent = label;
  76.  
  77. const switchContainer = document.createElement('label');
  78. switchContainer.className = 'toggle-switch';
  79.  
  80. const input = document.createElement('input');
  81. input.type = 'checkbox';
  82. input.checked = checked;
  83. input.className = 'toggle-input';
  84. input.addEventListener('change', onChange);
  85.  
  86. const slider = document.createElement('span');
  87. slider.className = 'toggle-slider';
  88.  
  89. switchContainer.appendChild(input);
  90. switchContainer.appendChild(slider);
  91. container.appendChild(labelElement);
  92. container.appendChild(switchContainer);
  93.  
  94. return container;
  95. }
  96.  
  97.  
  98. /**
  99. * Creates and returns a settings menu.
  100. * @returns {HTMLDivElement}
  101. */
  102. function createSettingsMenu() {
  103. const menu = document.createElement('div');
  104. menu.id = 'settings-menu';
  105. menu.className = 'settings-dropdown';
  106. menu.style.display = 'none';
  107.  
  108. // Create toggle switches
  109. const modelSwitcherToggle = createToggleSwitch('Model Switcher', settings.modelSwitcher, (e) => {
  110. settings.modelSwitcher = e.target.checked;
  111. saveSettings(settings);
  112. updateModelSwitcherVisibility();
  113. });
  114. const streamerModeToggle = createToggleSwitch(
  115. 'Streamer Mode',
  116. settings.streamerMode ?? true,
  117. (e) => {
  118. settings.streamerMode = e.target.checked;
  119. saveSettings(settings);
  120. updateStreamerModeStyles();
  121. }
  122. );
  123.  
  124. const animationsToggle = createToggleSwitch('Animations', settings.animations, (e) => {
  125. settings.animations = e.target.checked;
  126. saveSettings(settings);
  127. updateAnimationStyles();
  128. });
  129.  
  130. menu.appendChild(modelSwitcherToggle);
  131. menu.appendChild(streamerModeToggle);
  132. menu.appendChild(animationsToggle);
  133.  
  134. // Append menu to body to avoid positioning issues
  135. document.body.appendChild(menu);
  136.  
  137. return menu;
  138. }
  139.  
  140. /**
  141. * Creates and returns a <button> element with an attached settings menu.
  142. * @param {div} menu - The settings menu to be attached
  143. * @returns {HTMLButtonElement}
  144. */
  145. function createSettingsCog(menu) {
  146. const cog = document.createElement('button');
  147. cog.id = 'settings-cog';
  148. //cog.textContent = settings.animations ? '⚙️' : '⚙';
  149. cog.setAttribute('aria-label', 'Settings');
  150.  
  151. // Toggle menu visibility
  152. cog.addEventListener('click', (e) => {
  153. e.stopPropagation();
  154. //const isVisible = window.getComputedStyle(menu).display !== 'none';
  155. if (menu.style.display === 'block')
  156. {
  157. menu.style.display = 'none';
  158. }
  159. else {
  160. positionMenu();
  161. menu.style.display = 'block';
  162. }
  163. });
  164.  
  165. // Close menu when clicking outside
  166. document.addEventListener('click', (e) => {
  167. if (!cog.contains(e.target) && !menu.contains(e.target)) {
  168. menu.style.display = 'none';
  169. }
  170. });
  171.  
  172. // Position menu relative to cog
  173. function positionMenu() {
  174. // cog bounds, changes when cog is rotated (animations enabled) -> alignment inconsistencies
  175. const cogRect = cog.getBoundingClientRect();
  176. // page header bounds
  177. const parentRect = cog.parentElement.getBoundingClientRect();
  178. const viewportWidth = window.innerWidth;
  179.  
  180. menu.style.position = 'fixed';
  181. menu.style.top = `${parentRect.bottom - 5}px`; // 5px above `page-header`
  182. menu.style.zIndex = '10000';
  183. const cogRight = cogRect.left + cogRect.width;
  184. const rightOffset = viewportWidth - cogRight;
  185.  
  186. // prepare initial state
  187. menu.style.right = `${rightOffset}px`;
  188. menu.style.left = 'auto';
  189. if (settings.animations) {
  190. menu.style.opacity = '0';
  191. menu.style.transform = 'translateX(10px)';
  192. menu.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
  193.  
  194. /*// force a reflow so the browser registers the start state
  195. // eslint-disable-next-line @microsoft/sdl/no-document-domain -- reflow hack
  196. void menu.offsetWidth;*/
  197.  
  198. // slide into place
  199. requestAnimationFrame(() => {
  200. menu.style.opacity = '1';
  201. menu.style.transform = 'translateX(0)';
  202. });
  203. }
  204. }
  205.  
  206. // Inject CSS for settings menu and toggle switches
  207. injectSettingsStyles();
  208.  
  209. return cog;
  210. }
  211.  
  212. /**
  213. * Injects CSS styles for the settings menu and components
  214. */
  215. function injectSettingsStyles() {
  216. if (document.getElementById('settings-styles')) return;
  217.  
  218. const style = document.createElement('style');
  219. style.id = 'settings-styles';
  220.  
  221. style.textContent = `
  222. #settings-cog {
  223. font-size: 20px;
  224. margin-left: 12px;
  225. padding: 4px 5px;
  226. border: none;
  227. border-radius: 50%;
  228. background-color: #212121;
  229. color: #fff;
  230. cursor: pointer;
  231. box-shadow: 0 0 0 0 rgba(33, 33, 33, 0) inset,
  232. 0 0 5px 0 rgba(33, 33, 33, 0);
  233. display: flex;
  234. align-items: center;
  235. justify-content: center;
  236. position: relative;
  237. transform: translateX(0.75px) translateY(-0.75px);
  238. transition: background-color var(--anim-fast) var(--easing-standard),
  239. box-shadow var(--anim-slow) var(--easing-standard),
  240. transform var(--anim-normal) var(--easing-transform);
  241. }
  242. #settings-cog:hover {
  243. background-color: #2f2f2f;
  244. box-shadow: 0 0 2.5px 0 rgba(255, 255, 255, 0) inset,
  245. 0 0 5px 0 rgba(255, 255, 255, 0.2);
  246. transform: translateX(0.75px) translateY(-0.75px) var(--cog-rotate);
  247. }
  248. #settings-cog:focus {
  249. outline: none;
  250. box-shadow: 0 0 2.5px 0 rgba(255, 255, 255, 0.5) inset,
  251. 0 0 5px 0 rgba(255, 255, 255, 0.5);
  252. }
  253.  
  254. #settings-cog::before {
  255. content: var(--cog-icon);
  256. transform-origin: center;
  257. transform: translateX(0.75px) translateY(-0.75px);
  258. }
  259.  
  260. .settings-dropdown {
  261. display: none;
  262. background-color: #2a2a2a;
  263. border: 1px solid #444;
  264. border-radius: 8px;
  265. padding: 12px;
  266. min-width: 200px;
  267. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  268. }
  269.  
  270.  
  271. .toggle-container {
  272. display: flex;
  273. justify-content: space-between;
  274. align-items: center;
  275. margin-bottom: 12px;
  276. }
  277. .toggle-container:last-child {
  278. margin-bottom: 0;
  279. }
  280.  
  281. .toggle-label {
  282. color: #fff;
  283. font-size: 14px;
  284. }
  285.  
  286. .toggle-switch {
  287. position: relative;
  288. display: inline-block;
  289. width: 44px;
  290. height: 24px;
  291. }
  292.  
  293. .toggle-input {
  294. position: absolute;
  295. opacity: 0;
  296. width: 100%;
  297. height: 100%;
  298. cursor: pointer;
  299. z-index: 1;
  300. }
  301. .toggle-input:checked + .toggle-slider {
  302. background-color: #4CAF50;
  303. }
  304. .toggle-input:checked + .toggle-slider:before {
  305. transform: translateX(20px);
  306. }
  307. .toggle-input:checked + .toggle-slider:hover {
  308. background-color: #45a049;
  309. }
  310.  
  311. .toggle-slider {
  312. position: absolute;
  313. top: 0;
  314. left: 0;
  315. right: 0;
  316. bottom: 0;
  317. background-color: #555;
  318. border-radius: 24px;
  319. transition: background-color var(--anim-normal) var(--easing-slider-bg),
  320. transform var(--anim-normal) var(--easing-transform);
  321. }
  322. .toggle-slider:before {
  323. content: "";
  324. position: absolute;
  325. height: 18px;
  326. width: 18px;
  327. left: 3px;
  328. bottom: 3px;
  329. background-color: white;
  330. border-radius: 50%;
  331. transition: transform var(--anim-normal) var(--easing-transform);
  332. }
  333. `;
  334. document.head.appendChild(style);
  335. }
  336.  
  337. /**
  338. * Updates animation styles based on current settings with CSS custom properties
  339. */
  340. function updateAnimationStyles() {
  341. const root = document.documentElement;
  342. const animate = settings.animations;
  343. // Durations
  344. root.style.setProperty('--anim-fast', animate ? '0.2s' : '0s');
  345. root.style.setProperty('--anim-normal', animate ? '0.3s' : '0s');
  346. root.style.setProperty('--anim-slow', animate ? '0.4s' : '0s');
  347. // Easing functions
  348. root.style.setProperty('--easing-standard', animate ? 'cubic-bezier(0.4, 0, 0.2, 1)' : '');
  349. root.style.setProperty('--easing-transform', animate ? 'cubic-bezier(0.68, -0.55, 0.27, 1.55)' : '');
  350. root.style.setProperty('--easing-slider-bg', animate ? 'cubic-bezier(0.68, -0.1, 0.27, 1.1)' : '');
  351. // Cog styles
  352. root.style.setProperty('--cog-rotate', animate ? 'rotate(45deg)' : 'rotate(0deg)');
  353. root.style.setProperty('--cog-icon', animate ? "'⚙️'" : "'⚙'");
  354. // Model Switcher glow (initially invisible, transitions from this on hover)
  355. root.style.setProperty('--initial-glow', animate ? `0 0 0 0 rgba(33, 33, 33, 0) inset,
  356. 0 0 5px 0 rgba(33, 33, 33, 0)` : 'none');
  357. }
  358.  
  359. function updateStreamerModeStyles() {
  360. injectStreamerModeStyles();
  361. document.body.classList.toggle('streamer-mode', settings.streamerMode);
  362. }
  363.  
  364. function injectStreamerModeStyles() {
  365. if (document.getElementById('streamer-styles')) return;
  366.  
  367. const style = document.createElement('style');
  368. style.id = 'streamer-styles';
  369.  
  370. style.textContent = `
  371. /* inactive chats */
  372. .streamer-mode #history .__menu-item:not([data-active]) {
  373. box-shadow: 0 0 2.5px 0 rgba(255, 255, 255, 0) inset,
  374. 0 0 5px 0 rgba(255, 255, 255, 0.2);
  375. transition: background-color var(--anim-fast) var(--easing-standard),
  376. box-shadow var(--anim-slow) var(--easing-standard);
  377. }
  378. /* inactive chat titles */
  379. .streamer-mode #history .__menu-item:not([data-active]) .truncate span {
  380. opacity: 0;
  381. transition: opacity var(--anim-fast) var(--easing-standard),
  382. box-shadow var(--anim-slow) var(--easing-standard);
  383. }
  384. .streamer-mode #history .__menu-item:not([data-active]):hover .truncate span {
  385. opacity: 1;
  386. }
  387. /* accounts profile */
  388. .streamer-mode [data-testid="accounts-profile-button"] {
  389. display: none !important;
  390. }
  391. `;
  392.  
  393. document.head.appendChild(style);
  394. }
  395.  
  396. /**
  397. * Updates model switcher visibility based on settings
  398. */
  399. function updateModelSwitcherVisibility() {
  400. const modelSwitcher = document.getElementById('chatgpt-model-switcher');
  401. if (modelSwitcher) {
  402. modelSwitcher.style.display = settings.modelSwitcher ? 'block' : 'none';
  403. }
  404. }
  405.  
  406. /**
  407. * Injects CSS styles for the model switcher
  408. */
  409. function injectModelSwitcherStyles() {
  410. if (document.getElementById('model-switcher-styles')) return;
  411.  
  412. const style = document.createElement('style');
  413. style.id = 'model-switcher-styles';
  414.  
  415. style.textContent = `
  416. #chatgpt-model-switcher {
  417. margin: auto;
  418. padding: 4px 8px;
  419. border: none;
  420. border-radius: 6px;
  421. background-color: #212121;
  422. color: #fff;
  423. outline: none;
  424. box-shadow: var(--initial-glow);
  425. transition: background-color var(--anim-fast) var(--easing-standard),
  426. box-shadow var(--anim-slow) var(--easing-standard);
  427. }
  428. #chatgpt-model-switcher:hover {
  429. background-color: #2f2f2f;
  430. box-shadow: 0 0 2.5px 0 rgba(255, 255, 255, 0) inset,
  431. 0 0 5px 0 rgba(255, 255, 255, 0.2);
  432. }
  433. #chatgpt-model-switcher:focus {
  434. outline: none;
  435. box-shadow: 0 0 2.5px 0 rgba(255, 255, 255, 0.5) inset,
  436. 0 0 5px 0 rgba(255, 255, 255, 0.5);
  437. }
  438. `;
  439. document.head.appendChild(style);
  440. }
  441.  
  442. /**
  443. * Creates and returns a <select> element configured as the model switcher.
  444. * @param {string} currentModel - Model to pre-select in the dropdown.
  445. * @returns {HTMLSelectElement}
  446. */
  447. function createModelSwitcher(currentModel) {
  448. const select = document.createElement('select');
  449. select.id = 'chatgpt-model-switcher';
  450.  
  451. // Inject CSS for base styling, hover, focus, and transition effects
  452. injectModelSwitcherStyles();
  453.  
  454. // Populate dropdown with model options
  455. MODELS.forEach(model => {
  456. const option = document.createElement('option');
  457. option.value = model;
  458. option.textContent = model;
  459. if (model === currentModel) option.selected = true;
  460. select.appendChild(option);
  461. });
  462.  
  463. // Save selection to localStorage on change
  464. select.addEventListener('change', () => {
  465. localStorage.setItem(PREFERRED_MODEL_KEY, select.value);
  466. });
  467.  
  468. // Set initial visibility based on settings
  469. select.style.display = settings.modelSwitcher ? 'block' : 'none';
  470.  
  471. return select;
  472. }
  473.  
  474. /**
  475. * Finds our model switcher in the UI and inserts the settings cog after it.
  476. * Retries every second until our model switcher is visible.
  477. */
  478. function injectSettingsMenu() {
  479. const checkInterval = setInterval(() => {
  480. const modelSwitcher = document.getElementById('chatgpt-model-switcher');
  481. if (!modelSwitcher) return; // Wait until the model switcher is available
  482.  
  483. let cog = document.getElementById('settings-cog');
  484. let menu = document.getElementById('settings-menu');
  485.  
  486. // Create menu if it doesn't exist yet
  487. if (!menu) {
  488. menu = createSettingsMenu();
  489. }
  490. // Create cog + Insert cog after visible model switcher (if it doesn't exist)
  491. if (!cog) {
  492. cog = createSettingsCog(menu);
  493. modelSwitcher.after(cog); // Use the more modern and readable .after()
  494. }
  495. }, 1000);
  496. }
  497.  
  498. /**
  499. * Finds the native model switcher in the UI and inserts our custom switcher beside it.
  500. * Retries every second until the native element is visible.
  501. */
  502. function injectModelSwitcher() {
  503. const checkInterval = setInterval(() => {
  504. const nativeModelSwitchers = document.querySelectorAll('[data-testid="model-switcher-dropdown-button"]');
  505. let switcher = document.getElementById('chatgpt-model-switcher');
  506.  
  507. // Create switcher
  508. if (!switcher) {
  509. const savedModel = localStorage.getItem(PREFERRED_MODEL_KEY) || DEFAULT_MODEL;
  510. switcher = createModelSwitcher(savedModel);
  511. }
  512. // Insert switcher next to the first visible native button
  513. if (!switcher.parentNode) {
  514. for (let nativeModelSwitcher of nativeModelSwitchers) {
  515. if (nativeModelSwitcher.checkVisibility && nativeModelSwitcher.checkVisibility()) {
  516. nativeModelSwitcher.parentNode.after(switcher);
  517. break;
  518. }
  519. }
  520. }
  521. }, 1000);
  522. }
  523.  
  524. /**
  525. * Overrides window.fetch to intercept conversation requests and replace the model
  526. * property in the request body with the user-selected model.
  527. */
  528. function overrideModelInRequest() {
  529. // Only override if model switcher is enabled
  530. if (!settings.modelSwitcher) return;
  531.  
  532. const origFetch = window.fetch;
  533. window.fetch = async (...args) => {
  534. const [resource, config] = args;
  535. const savedModel = localStorage.getItem(PREFERRED_MODEL_KEY) || DEFAULT_MODEL;
  536.  
  537. // Target only conversation API calls
  538. if (
  539. typeof resource === 'string' &&
  540. resource.includes('/backend-api/f/conversation') &&
  541. config?.body
  542. ) {
  543. try {
  544. const body = JSON.parse(config.body);
  545. if (body && body.model) {
  546. // Overwrite model
  547. body.model = savedModel;
  548. config.body = JSON.stringify(body);
  549. }
  550. } catch (e) {
  551. console.warn('Model switcher failed to parse request body', e);
  552. }
  553. }
  554.  
  555. return origFetch(resource, config);
  556. };
  557. }
  558.  
  559. // Initialize the userscript
  560. injectModelSwitcher();
  561. overrideModelInRequest();
  562. updateStreamerModeStyles();
  563. injectSettingsMenu();
  564. updateAnimationStyles();
  565.  
  566. })();

QingJ © 2025

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