Greasy Fork

AI Studio Model Modifier

拦截 aistudio.google.com 的 GenerateContent 请求修改模型,支持在官方、预览及内部测试模型(例如 Kingfall)间切换,并提供带分类的下拉菜单。

// ==UserScript==
// @name:zh_cn   AI Studio 模型修改器 - 解锁 Kingfall 及更多隐藏模型
// @name         AI Studio Model Modifier
// @namespace    http://tampermonkey.net/
// @version      1.1.1
// @description  拦截 aistudio.google.com 的 GenerateContent 请求修改模型,支持在官方、预览及内部测试模型(例如 Kingfall)间切换,并提供带分类的下拉菜单。
// @description:en Modify the model for aistudio.google.com requests, allowing switching between official, preview, and internal test models such as kingfall with a categorized dropdown menu.
// @author       Z_06
// @match        *://aistudio.google.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=google.com
// @license      MIT
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @homepageURL  https://greasyfork.org/zh-CN/scripts/539130-ai-studio-model-modifier
// @supportURL   https://greasyfork.org/zh-CN/scripts/539130-ai-studio-model-modifier
// ==/UserScript==

(function() {
    'use strict';

    // --- 配置区域 ---
    const SCRIPT_NAME = "[AI Studio] 模型修改器";
    const STORAGE_KEY = "aistudio_custom_model_name_v2";
    const TARGET_URL = "https://alkalimakersuite-pa.clients6.google.com/$rpc/google.internal.alkali.applications.makersuite.v1.MakerSuiteService/GenerateContent";
    const MODEL_SELECTOR_CONTAINER = 'div.settings-model-selector';

    // 带分类的可选模型列表
    const MODEL_OPTIONS = [
        {
            label: "内部测试模型",
            options: [
                { name: "Kingfall (内部测试)", value: "models/kingfall-ab-test" },
                { name: "Calmriver (内部测试)", value: "models/calmriver-ab-test" },
                { name: "Claybrook (内部测试)", value: "models/claybrook-ab-test" },
                { name: "Frostwind (内部测试)", value: "models/frostwind-ab-test" },
                { name: "Goldmane (内部测试)", value: "models/goldmane-ab-test" },
            ]
        },
        {
            label: "Gemini 2.5",
            options: [
                { name: "2.5 Pro 预览版 (06-05)", value: "models/gemini-2.5-pro-preview-06-05" },
                { name: "2.5 Flash 预览版 (05-20)", value: "models/gemini-2.5-flash-preview-05-20" },
                { name: "2.5 Pro 预览 (05-06)", value: "models/gemini-2.5-pro-preview-05-06" },
                { name: "2.5 Pro 预览 (03-25)", value: "models/gemini-2.5-pro-preview-03-25" },
                { name: "2.5 Pro 预览 (03-25 AB-Test)", value: "models/gemini-2.5-pro-preview-03-25-ab-test" },
                { name: "2.5 Pro EXP (03-25)", value: "models/gemini-2.5-pro-exp-03-25" },
                { name: "2.5 Flash 预览 (04-17)", value: "models/gemini-2.5-flash-preview-04-17" },
                { name: "2.5 Flash 预览 (04-17 Thinking)", value: "models/gemini-2.5-flash-preview-04-17-thinking" },
            ]
        },
        {
            label: "Gemini 2.0",
            options: [
                { name: "2.0 Flash", value: "models/gemini-2.0-flash" },
                { name: "2.0 Flash (图片生成)", value: "models/gemini-2.0-flash-preview-image-generation" },
                { name: "2.0 Flash-Lite", value: "models/gemini-2.0-flash-lite" },
            ]
        },
        {
            label: "Gemini 1.5",
            options: [
                { name: "1.5 Pro", value: "models/gemini-1.5-pro" },
                { name: "1.5 Flash", value: "models/gemini-1.5-flash" },
                { name: "1.5 Flash-8B", value: "models/gemini-1.5-flash-8b" },
            ]
        }
    ];
    // 默认模型
    const DEFAULT_MODEL = MODEL_OPTIONS[0].options[0].value;

    // --- 获取已保存的或默认的模型名称 ---
    let customModelName = GM_getValue(STORAGE_KEY, DEFAULT_MODEL);

    // --- 注入CSS样式 ---
    GM_addStyle(`
        ${MODEL_SELECTOR_CONTAINER} ms-model-selector-two-column { display: none !important; }
        #custom-model-selector {
            width: 100%; padding: 8px 12px; margin-top: 4px; border: 1px solid #5f6368;
            border-radius: 8px; color: #e2e2e5; background-color: #35373a;
            font-family: 'Google Sans', 'Roboto', sans-serif; font-size: 14px; font-weight: 500;
            box-sizing: border-box; cursor: pointer; -webkit-appearance: none; -moz-appearance: none; appearance: none;
            background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23e2e2e5%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.4-5.4-13z%22%2F%3E%3C%2Fsvg%3E');
            background-repeat: no-repeat; background-position: right 12px center; background-size: 10px;
        }
        #custom-model-selector optgroup { font-weight: bold; color: #8ab4f8; }
    `);

    // --- 菜单和UI更新逻辑 ---

    /**
     * 更新或创建下拉菜单中的选项,并选中指定的模型
     * @param {string} modelValue - 要选中的模型的完整值
     */
    function updateAndSelectModel(modelValue) {
        const selector = document.getElementById('custom-model-selector');
        if (!selector) return;

        // 使用 querySelector 检查选项是否已存在于任何分组中
        if (!selector.querySelector(`option[value="${modelValue}"]`)) {
            // 选项不存在,动态创建它到 "自定义模型" 分组
            let customGroup = document.getElementById('custom-model-optgroup');
            if (!customGroup) {
                customGroup = document.createElement('optgroup');
                customGroup.id = 'custom-model-optgroup';
                customGroup.label = '自定义模型';
                selector.appendChild(customGroup);
            }

            const newOption = document.createElement('option');
            newOption.value = modelValue;
            newOption.textContent = `* ${modelValue.replace('models/', '')}`;
            customGroup.appendChild(newOption);
        }

        selector.value = modelValue;
    }

    /**
     * 创建并注入带分类的模型选择下拉菜单
     * @param {HTMLElement} container - 用于注入UI的父容器元素
     */
    function createModelSelectorUI(container) {
        console.log(`[${SCRIPT_NAME}] 发现容器,注入带分类的UI...`);

        const selector = document.createElement('select');
        selector.id = 'custom-model-selector';
        selector.title = "所有请求都将被强制使用此下拉框选中的模型";

        // 遍历分类和选项来创建 <optgroup> 和 <option>
        MODEL_OPTIONS.forEach(group => {
            const optgroup = document.createElement('optgroup');
            optgroup.label = group.label;
            group.options.forEach(opt => {
                const option = document.createElement('option');
                option.value = opt.value;
                option.textContent = opt.name;
                optgroup.appendChild(option);
            });
            selector.appendChild(optgroup);
        });

        selector.addEventListener('change', (event) => {
            const newModel = event.target.value;
            customModelName = newModel;
            GM_setValue(STORAGE_KEY, newModel);
            console.log(`[${SCRIPT_NAME}] 模型已切换并保存: ${newModel}`);
        });

        const injectionPoint = container.querySelector('.item-input-form-field');
        if (injectionPoint) {
            injectionPoint.appendChild(selector);
            // 初始化UI,确保它显示当前活动的模型(如果不在预设中,会动态添加)
            updateAndSelectModel(customModelName);
            console.log(`[${SCRIPT_NAME}] 自定义UI注入成功。`);
        }
    }

    // 注册油猴菜单命令
    GM_registerMenuCommand(`添加/设置自定义模型`, () => {
        const newModel = prompt("请输入要强制使用的完整模型名称:", customModelName);
        if (newModel && newModel.trim() !== "") {
            const trimmedModel = newModel.trim();
            customModelName = trimmedModel;
            GM_setValue(STORAGE_KEY, trimmedModel);
            alert(`模型已更新为:\n${trimmedModel}`);
            updateAndSelectModel(trimmedModel);
        }
    });

    // --- DOM 变动监听,用于注入UI元素 ---
    const observer = new MutationObserver((mutations, obs) => {
        const container = document.querySelector(MODEL_SELECTOR_CONTAINER);
        if (container && !document.getElementById('custom-model-selector')) {
            createModelSelectorUI(container);
        }
    });
    observer.observe(document.body, { childList: true, subtree: true });

    // --- 核心:拦截和修改 XHR 请求 ---
    const originalOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(method, url) {
        this._url = url;
        this._method = method;
        return originalOpen.apply(this, arguments);
    };

    const originalSend = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.send = function(data) {
        if (this._url === TARGET_URL && this._method.toUpperCase() === 'POST' && data) {
            try {
                let payload = JSON.parse(data);
                const originalModel = payload[0];
                if (typeof originalModel === 'string' && originalModel.startsWith('models/')) {
                    console.log(`[${SCRIPT_NAME}] 拦截请求。原始: ${originalModel} -> 修改为: ${customModelName}`);
                    payload[0] = customModelName;
                    const modifiedData = JSON.stringify(payload);
                    return originalSend.call(this, modifiedData);
                }
            } catch (e) {
                console.error(`[${SCRIPT_NAME}] 修改请求负载时出错:`, e);
            }
        }
        return originalSend.apply(this, arguments);
    };

    console.log(`[${SCRIPT_NAME}] 已加载。当前强制模型为 "${customModelName}"。`);
})();