ChatGPT model picker + extras

A script that seemlessly adds a model picker to the ChatGPT UI

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==UserScript==
// @name         ChatGPT model picker + extras
// @author       Worthy
// @match        https://chatgpt.com/
// @match        https://chatgpt.com/c/*
// @grant        unsafeWindow
// @grant        GM.getValue
// @grant        GM.setValue
// @run-at       document-start
// @version      1.02
// @license      MIT
// @namespace https://greasyfork.org/users/1569445
// @description A script that seemlessly adds a model picker to the ChatGPT UI
// ==/UserScript==

(function() {
    'use strict';

    // Default state
    let selectedModel = "gpt-5-2";
    let currentButtonName = "5.2 Instant";

    // Async load saved preferences
    (async () => {
        selectedModel = await GM.getValue("model", "gpt-5-2");
        currentButtonName = await GM.getValue("modelLabel", "5.2 Instant");
    })();

    /* ==========================================================================================
       THE BRAINS (Logic from Script 2)
       ========================================================================================== */

    function patchPayload(jsonString) {
        try {
            const body = JSON.parse(jsonString);

            // 1. Force top-level model identifiers
            body.model = selectedModel;
            if (body.model_slug) body.model_slug = selectedModel;

            // 2. Force conversation_mode (The critical fix)
            if (body.conversation_mode) {
                if (typeof body.conversation_mode === 'object') {
                    body.conversation_mode.model = selectedModel;
                    if (!body.conversation_mode.kind) {
                         body.conversation_mode.kind = "primary_assistant";
                    }
                }
            } else {
                body.conversation_mode = {
                    kind: "primary_assistant",
                    model: selectedModel
                };
            }

            // 3. Nuke specific requirements that might reset the model
            if (body.requirements) {
                 delete body.requirements.lat;
                 delete body.requirements.long;
            }

            console.log(`[Model Switcher] Patched request to: ${selectedModel}`);
            return JSON.stringify(body);

        } catch (e) {
            console.error("Payload Patch Error:", e);
            return jsonString;
        }
    }

    const originalFetch = unsafeWindow.fetch;
    unsafeWindow.fetch = async function (input, init) {
        // Case 1: Standard fetch(url, options)
        if (typeof input === "string" && input.includes("/conversation") && init && init.body) {
            init.body = patchPayload(init.body);
        }
        // Case 2: fetch(Request) - frequently used by newer React builds
        else if (input instanceof Request && input.url.includes("/conversation")) {
            try {
                const clone = input.clone();
                const text = await clone.text();
                const newBody = patchPayload(text);

                input = new Request(input, {
                    body: newBody,
                    method: input.method,
                    headers: input.headers,
                    referrer: input.referrer,
                    referrerPolicy: input.referrerPolicy,
                    mode: input.mode,
                    credentials: input.credentials,
                    cache: input.cache,
                    redirect: input.redirect,
                    integrity: input.integrity,
                });
            } catch (err) {
                console.error("Failed to patch Request object:", err);
            }
        }
        return originalFetch.call(this, input, init);
    };


    /* ==========================================================================================
       THE LOOKS (UI from Script 1)
       ========================================================================================== */

    function refreshUI() {
        // Remove original header label if it exists
        const originalLabel = document.querySelector('main .sticky.top-0 h1, main .sticky.top-0 div button[role="combobox"]');
        if (originalLabel) { originalLabel.style.display = 'none'; }

        // Find the insertion point
        const mainHeader = document.querySelector('main .sticky.top-0') || document.querySelector('header.sticky');
        if (!mainHeader || document.getElementById('phantom-naming-picker')) return;

        // Create Container
        const pickerWrap = document.createElement('div');
        pickerWrap.id = 'phantom-naming-picker';
        pickerWrap.style.cssText = "position: absolute; left: 14px; top: 50%; transform: translateY(-50%); z-index: 9999; background-color: #212121; border-radius: 8px;";

        // SVG Fixed: Removed the extra quotes and brackets from your original snippet
        pickerWrap.innerHTML = `
            <div style="position: relative; font-family: Söhne Buch, ui-sans-serif, system-ui;">
                <button id="phantom-btn" style="background: #212121; border: none; color: #ffffff; font-weight: 400; font-size: 18px; cursor: pointer; display: flex; align-items: center; gap: 4px; padding: 6px 3.5px; border-radius: 8px; white-space: nowrap;">
                    ChatGPT <span id="current-model-label" style="color: #b4b4b4; margin-left: 2px;">${currentButtonName}</span>
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" style="opacity: 0.5; margin-left: 4px;"><path d="m6 9 6 6 6-6"/></svg>
                </button>

                <div id="phantom-menu" style="display: none; position: absolute; top: 115%; left: 0; background: #2f2f2f; border: 1px solid #424242; border-radius: 12px; padding: 4px; width: 220px; box-shadow: 0 10px 25px rgba(0,0,0,0.5);">
                    <div style="padding: 8px 12px; font-size: 11px; font-weight: 600; color: #8e8e93; text-transform: uppercase; letter-spacing: 0.5px;">Latest models</div>

                    ${createCategory("GPT-5.2", [
                        {id: 'gpt-5-2', name: 'GPT-5.2 Instant', display: '5.2 Instant', desc: 'Responds quickly for general chat'},
                        {id: 'gpt-5-2-thinking', name: 'GPT-5.2 Thinking', display: '5.2 Thinking', desc: 'Uses advanced reasoning'},
                        {id: 'gpt-5-2-pro', name: 'GPT-5.2 Pro', display: '5.2 Pro', desc: 'Best at reasoning'},
                        {id: 'auto', name: 'GPT-5.2 Auto', display: '5.2 Auto', desc: 'Automatically adjusts thinking power'}
                    ])}
                    ${createCategory("GPT-5.1", [
                        {id: 'gpt-5-1', name: 'GPT-5.1 Instant', display: '5.1 Instant', desc: 'Great for general chat'},
                        {id: 'gpt-5-1-thinking', name: 'GPT-5.1 Thinking', display: '5.1 Thinking', desc: 'Uses advanced reasoning'},
                        {id: 'gpt-5-1-pro', name: 'GPT-5.1 Pro', display: '5.1 Pro', desc: 'Previous best at reasoning'}
                    ])}
                    ${createCategory("GPT-5", [
                        {id: 'gpt-5', name: 'GPT-5 Instant', display: '5 Instant', desc: 'Previous flagship for general chat'},
                        {id: 'gpt-5-thinking', name: 'GPT-5 Thinking', display: '5 Thinking', desc: 'Uses advanced reasoning'},
                        {id: 'gpt-5-pro', name: 'GPT-5 Pro', display: '5 Pro', desc: 'Previous best at reasoning'},
                        {id: 'gpt-5-mini', name: 'GPT-5 Mini', display: '5-mini', desc: 'Faster for everyday tasks'}
                    ])}

                    <div style="height: 1px; background: #424242; margin: 4px 8px;"></div>
                    <div style="padding: 8px 12px; font-size: 11px; font-weight: 600; color: #8e8e93; text-transform: uppercase; letter-spacing: 0.5px;">Legacy models</div>

                    ${createCategory("Chat", [
                        {id: 'gpt-4o', name: 'GPT-4o', display: '4o', desc: 'Great for most tasks'},
                        {id: 'gpt-4-1', name: 'GPT-4.1', display: '4.1', desc: 'Great for quick coding'},
                        {id: 'gpt-4-5', name: 'GPT-4.5', display: '4.5', desc: 'Powerful for creative writing'},
                        {id: 'gpt-4-1-mini', name: 'GPT-4.1-mini', display: '4.1-mini', desc: 'Faster for structured tasks'},
                        {id: 'gpt-4o-mini', name: 'GPT-4o-mini', display: '4o-mini', desc: 'Faster for general tasks'}
                    ])}
                    ${createCategory("Thinking", [
                        {id: 'o3', name: 'o3', display: 'o3', desc: 'Uses advanced reasoning'},
                        {id: 'o4-mini', name: 'o4-mini', display: 'o4-mini', desc: 'Fastest at reasoning'},
                        {id: 'o3-mini', name: 'o3-mini', display: 'o3-mini', desc: 'Previous fastest at reasoning'},
                        {id: 'o1', name: 'o1', display: 'o1', desc: 'First to do reasoning'},
                        {id: 'o1-mini', name: 'o1-mini', display: 'o1-mini', desc: 'First at speed-optimised reasoning'},
                    ])}
                    ${createCategory("Ultra-legacy", [
                        {id: 'gpt-4-turbo', name: 'GPT-4-Turbo', display: '4 Turbo', desc: 'Faster for high-intelligence tasks'},
                        {id: 'gpt-4', name: 'GPT-4', display: '4', desc: 'Made for high-intelligence tasks'},
                        {id: 'gpt-3-5', name: 'GPT-3.5', display: '3.5', desc: 'First to be used in ChatGPT'},
                        {id: 'text-davinci-001', name: 'GPT-3', display: '3', desc: 'First to have human-like speech'},
                        {id: 'gpt2', name: 'GPT-2', display: '2', desc: 'First large-scale GPT'},
                        {id: 'openai-gpt', name: 'GPT-1', display: '1', desc: 'First ever GPT'}
                    ])}
                </div>
            </div>
        `;

        mainHeader.appendChild(pickerWrap);
        setupLogic();
    }

    function createCategory(title, models) {
        let subItems = models.map(m => `
            <div class="model-opt" data-id="${m.id}" data-btn-label="${m.display}" style="padding: 8px 12px; cursor: pointer; border-radius: 8px; color: #ececf1; display: flex; flex-direction: column; white-space: nowrap;">
                <span style="font-weight: 500; font-size: 13px;">${m.name}</span>
                <span style="font-size: 10px; color: #b4b4b4;">${m.desc}</span>
            </div>
        `).join('');

        return `
            <div class="cat-item" style="position: relative; padding: 10px 12px; cursor: default; border-radius: 8px; color: #ececf1; font-size: 14px; display: flex; align-items: center; justify-content: space-between;">
                ${title}
                <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" style="opacity: 0.5;"><path d="m9 18 6-6-6-6"/></svg>
                <div class="sub-menu" style="display: none; position: absolute; left: 98%; top: -4px; background: #2f2f2f; border: 1px solid #424242; border-radius: 12px; padding: 4px; width: 230px; box-shadow: 10px 10px 25px rgba(0,0,0,0.5);">
                    ${subItems}
                </div>
            </div>
        `;
    }

    function setupLogic() {
        const btn = document.getElementById('phantom-btn');
        const menu = document.getElementById('phantom-menu');
        const label = document.getElementById('current-model-label');

        btn.onclick = (e) => { e.stopPropagation(); menu.style.display = menu.style.display === 'none' ? 'block' : 'none'; };
        document.addEventListener('click', () => menu.style.display = 'none');

        document.querySelectorAll('.cat-item').forEach(cat => {
            const sub = cat.querySelector('.sub-menu');
            cat.onmouseenter = () => { sub.style.display = 'block'; cat.style.background = '#3e3e3e'; };
            cat.onmouseleave = () => { sub.style.display = 'none'; cat.style.background = 'none'; };
        });

        document.querySelectorAll('.model-opt').forEach(opt => {
            opt.onmouseover = (e) => { e.stopPropagation(); opt.style.background = '#4e4e4e'; };
            opt.onmouseout = () => opt.style.background = 'none';
            opt.onclick = async (e) => {
                e.stopPropagation();

                // Update State
                selectedModel = opt.getAttribute('data-id');
                currentButtonName = opt.getAttribute('data-btn-label');

                // Update UI
                label.innerText = currentButtonName;
                menu.style.display = 'none';

                // Save to Storage (So it remembers after refresh)
                await GM.setValue("model", selectedModel);
                await GM.setValue("modelLabel", currentButtonName);

                console.log(`[UI] Switched to ${selectedModel}`);
            };
        });
    }

    // Keep the UI alive
    setInterval(refreshUI, 500);
})();