You.com 模型选择优化 (现代化UI)

现代化模型选择下拉框,记忆上次使用的模型,自动同步模型列表变化,添加联网搜索开关

目前为 2025-03-26 提交的版本。查看 最新版本

// ==UserScript==
// @name         You.com 模型选择优化 (现代化UI)
// @namespace    http://tampermonkey.net/
// @version      1.6
// @description  现代化模型选择下拉框,记忆上次使用的模型,自动同步模型列表变化,添加联网搜索开关
// @author       ice lover
// @match        https://you.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=you.com
// @grant        GM_setValue
// @grant        GM_getValue
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // 配置
    const config = {
        debug: false,  // 设置为true开启调试日志
        selectorId: 'youcom-modern-selector',
        switchDelay: 600,
        styleId: 'youcom-selector-styles',
        defaultModel: 'Claude 3.7 Sonnet', // 默认模型
        storageKey: 'last-used-model',     // 存储键名
        webAccessKey: 'web-access-state',  // 联网状态存储键名
        modelContainerSelectors: [         // 模型按钮可能所在的容器选择器
            'div[class*="model-switcher"]',
            'div[class*="model-selector"]',
            'footer',
            'body'
        ]
    };

    // 模型列表
    const validModels = [
        'GPT-4o', 'Smart', 'Claude 3.7 Sonnet', 'Claude 3.5 Sonnet',
        'o3 Mini (High Effort)', 'GPT-4.5 Preview', 'Claude 3 Sonnet',
        'Claude 3 Opus', 'Claude 3 Haiku', 'Claude 2', 'Claude Instant', 'o3 Mini', 'o1',
        'QwQ 32B', 'Qwen2.5 72B', 'Qwen2.5 Coder 32B', 'DeepSeek-R1', 'DeepSeek-V3',
        'Grok 2', 'Llama 3.3 70B', 'Llama 3.2 90B', 'Llama 3.1 405B', 'Mistral Large 2',
        'Gemini 2.0 Flash', 'Gemini 1.5 Flash', 'Gemini 1.5 Pro', 'DBRX-Instruct',
        'Command R+', 'Solar 1 Mini', 'Dolphin 2.5'
    ];

    // 调试日志
    function log(...args) {
        if (config.debug) console.log('[You.com模型选择优化]', ...args);
    }

    // 保存最近使用的模型
    function saveLastUsedModel(model) {
        try {
            if (typeof GM_setValue === 'function') {
                GM_setValue(config.storageKey, model);
                log('已保存最近使用的模型:', model);
            } else {
                localStorage.setItem(config.storageKey, model);
                log('已保存最近使用的模型(localStorage):', model);
            }
        } catch (e) {
            log('保存模型失败:', e);
        }
    }

    // 获取最近使用的模型
    function getLastUsedModel() {
        try {
            let model;
            if (typeof GM_getValue === 'function') {
                model = GM_getValue(config.storageKey);
            } else {
                model = localStorage.getItem(config.storageKey);
            }
            if (model && validModels.includes(model)) {
                log('已获取最近使用的模型:', model);
                return model;
            }
        } catch (e) {
            log('获取模型失败:', e);
        }
        log('使用默认模型:', config.defaultModel);
        return config.defaultModel;
    }

    // 保存联网搜索状态
    function saveWebAccessState(enabled) {
        try {
            if (typeof GM_setValue === 'function') {
                GM_setValue(config.webAccessKey, enabled);
                log('已保存联网搜索状态:', enabled);
            } else {
                localStorage.setItem(config.webAccessKey, enabled);
                log('已保存联网搜索状态(localStorage):', enabled);
            }
        } catch (e) {
            log('保存联网搜索状态失败:', e);
        }
    }

    // 获取联网搜索状态
    function getWebAccessState() {
        try {
            let state;
            if (typeof GM_getValue === 'function') {
                state = GM_getValue(config.webAccessKey, true); // 默认为true
            } else {
                state = localStorage.getItem(config.webAccessKey);
                state = state === null ? true : state === 'true';
            }
            log('已获取联网搜索状态:', state);
            return state;
        } catch (e) {
            log('获取联网搜索状态失败:', e);
            return true; // 默认为true
        }
    }

    // 添加全局样式
    function addGlobalStyles() {
        // 移除旧样式(如果存在)
        const oldStyle = document.getElementById(config.styleId);
        if (oldStyle) oldStyle.remove();

        const style = document.createElement('style');
        style.id = config.styleId;
        style.textContent = `
            @keyframes youcom-spin { to { transform: rotate(360deg); } }

            /* 隐藏原始模型选择器 - 保持DOM存在但视觉隐藏 */
            div[data-testid="mode-switcher-chips"],
            div[class*="sc-17cb04c2-1"],
            div.sc-17cb04c2-0.hfTcaY,
            div.kqQJqI,
            [data-testid="mode-switcher-chips"] {
                opacity: 0 !important;
                position: absolute !important;
                top: -9999px !important;
                left: -9999px !important;
                height: 1px !important;
                width: 1px !important;
                overflow: hidden !important;
            }

            .youcom-loading {
                display: inline-block;
                width: 14px;
                height: 14px;
                border: 2px solid rgba(0,0,0,0.1);
                border-radius: 50%;
                border-top-color: #555;
                animation: youcom-spin 0.8s linear infinite;
            }

            #${config.selectorId} {
                position: fixed;
                top: 15px;
                left: calc(50% + 100px);
                transform: translateX(-50%);
                z-index: 9999;
                font-family: -apple-system, BlinkMacSystemFont, sans-serif;
                display: flex;
                align-items: center;
                gap: 10px;
            }

            #${config.selectorId} .youcom-dropdown {
                position: relative;
                display: inline-block;
                min-width: 140px;
            }

            #${config.selectorId} .youcom-selected {
                padding: 8px 12px;
                background: white;
                border: 1px solid #e0e0e0;
                border-radius: 6px;
                color: #333;
                cursor: pointer;
                display: flex;
                align-items: center;
                justify-content: space-between;
                box-shadow: 0 1px 2px rgba(0,0,0,0.05);
                transition: all 0.2s ease;
            }

            #${config.selectorId} .youcom-options {
                position: absolute;
                top: calc(100% + 5px);
                left: 0;
                width: 100%;
                background: white;
                border: 1px solid #e0e0e0;
                border-radius: 6px;
                box-shadow: 0 3px 10px rgba(0,0,0,0.1);
                display: none;
                z-index: 10000;
                max-height: 300px;
                overflow-y: auto;
            }

            #${config.selectorId} .youcom-option {
                padding: 10px 12px;
                cursor: pointer;
                color: #333;
                border-bottom: 1px solid #f0f0f0;
                display: flex;
                align-items: center;
                transition: all 0.2s;
            }

            #${config.selectorId} .youcom-option:hover {
                background: #f5f5f5;
            }

            #${config.selectorId} .youcom-option:last-child {
                border-bottom: none;
            }

            #${config.selectorId} .youcom-option-selected {
                background: #f8f8f8;
                font-weight: 500;
            }

            #${config.selectorId} .youcom-option-selected:hover {
                background: #f0f0f0;
            }

            #${config.selectorId} .youcom-model-name {
                font-weight: 500;
            }

            #${config.selectorId} .youcom-arrow {
                font-size: 12px;
                transition: transform 0.2s;
            }

            #${config.selectorId} .youcom-more-btn {
                padding: 8px 12px;
                background: white;
                border: 1px solid #e0e0e0;
                border-radius: 6px;
                color: #333;
                cursor: pointer;
                display: flex;
                align-items: center;
                gap: 6px;
                box-shadow: 0 1px 2px rgba(0,0,0,0.05);
                transition: all 0.2s ease;
            }

            #${config.selectorId} .youcom-more-btn:hover {
                background: #f5f5f5;
            }

            #${config.selectorId} .youcom-more-btn svg {
                width: 18px;
                height: 18px;
            }

            /* 联网搜索开关样式 */
            #${config.selectorId} .youcom-web-toggle {
                display: flex;
                align-items: center;
                gap: 6px;
                padding: 8px 12px;
                background: white;
                border: 1px solid #e0e0e0;
                border-radius: 6px;
                color: #333;
                cursor: pointer;
                box-shadow: 0 1px 2px rgba(0,0,0,0.05);
                transition: all 0.2s ease;
            }

            #${config.selectorId} .youcom-web-toggle:hover {
                background: #f5f5f5;
            }

            #${config.selectorId} .youcom-toggle-switch {
                position: relative;
                display: inline-block;
                width: 36px;
                height: 20px;
            }

            #${config.selectorId} .youcom-toggle-slider {
                position: absolute;
                cursor: pointer;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background-color: #ccc;
                transition: .4s;
                border-radius: 20px;
            }

            #${config.selectorId} .youcom-toggle-slider:before {
                position: absolute;
                content: "";
                height: 16px;
                width: 16px;
                left: 2px;
                bottom: 2px;
                background-color: white;
                transition: .4s;
                border-radius: 50%;
            }

            #${config.selectorId} .youcom-toggle-checkbox:checked + .youcom-toggle-slider {
                background-color: #2196F3;
            }

            #${config.selectorId} .youcom-toggle-checkbox:checked + .youcom-toggle-slider:before {
                transform: translateX(16px);
            }

            #${config.selectorId} .youcom-toggle-checkbox {
                opacity: 0;
                width: 0;
                height: 0;
                position: absolute;
            }

            #${config.selectorId} .youcom-web-label {
                font-size: 14px;
                font-weight: 500;
                white-space: nowrap;
            }
        `;
        document.head.appendChild(style);
    }

    // 清理模型文本
    function cleanModelText(text) {
        if (!text) return '';
        const cleaned = text.trim();
        for (const model of validModels) {
            if (cleaned.includes(model)) return model;
        }
        return cleaned === '更多模型...' ? '更多...' : cleaned;
    }

    // 获取当前选中模型
    function getCurrentModel() {
        const buttons = document.querySelectorAll('button');
        for (const button of buttons) {
            const text = cleanModelText(button.textContent);
            if (!text || !validModels.some(m => text.includes(m))) continue;

            const style = getComputedStyle(button);
            if (style.backgroundColor !== 'rgba(0, 0, 0, 0)') {
                log('选中的模型:', text);
                return text;
            }
        }
        return '选择模型';
    }

    // 获取所有模型按钮
    function getAllModelButtons() {
        return Array.from(document.querySelectorAll('button'))
            .filter(btn => {
                const text = cleanModelText(btn.textContent);
                return text && (validModels.some(m => text.includes(m)) || text === '更多...');
            })
            .filter(btn => btn.offsetParent !== null); // 只包含可见按钮
    }

    // 获取真正的"更多"按钮(排除侧边栏的More按钮)
    function getRealMoreButton() {
        const buttons = document.querySelectorAll('button[data-testid="mode-switcher-chips-more"]');
        for (const button of buttons) {
            // 确保是模型切换区域的More按钮,而不是侧边栏的
            if (button.closest('aside') === null && button.closest('nav') === null) {
                return button;
            }
        }
        return null;
    }

    // 是否是新对话页面
    function isNewChatPage() {
        return window.location.href.includes('/search') &&
               !window.location.href.includes('cid=') &&
               document.querySelector('input[type="text"][placeholder*="问问"]');
    }

    // 检测指定模型按钮
    function findModelButton(modelName) {
        const buttons = getAllModelButtons();
        return buttons.find(btn => cleanModelText(btn.textContent) === modelName);
    }

    // 自动选择上次使用的模型
    async function autoSelectLastUsedModel() {
        if (!isNewChatPage()) return false;

        log('检测到新对话页面');

        const lastModel = getLastUsedModel();
        const currentModel = getCurrentModel();

        log('当前模型:', currentModel, '上次使用的模型:', lastModel);

        if (currentModel !== lastModel) {
            const modelButton = findModelButton(lastModel);
            if (modelButton) {
                log('自动切换到上次使用的模型:', lastModel);
                modelButton.click();
                await new Promise(resolve => setTimeout(resolve, config.switchDelay));
                return true;
            } else {
                log('未找到上次使用的模型按钮:', lastModel);
            }
        } else {
            log('当前模型已经是上次使用的模型');
        }

        return false;
    }

    // 检查模型列表是否变化
    function hasModelListChanged() {
        const currentButtons = getAllModelButtons().map(btn => cleanModelText(btn.textContent));
        const selector = document.getElementById(config.selectorId);
        if (!selector) return true;

        const selectorButtons = Array.from(selector.querySelectorAll('.youcom-option'))
                                   .map(opt => opt.textContent.trim());

        if (currentButtons.length !== selectorButtons.length) {
            log('模型数量变化:', currentButtons.length, '->', selectorButtons.length);
            return true;
        }

        for (let i = 0; i < currentButtons.length; i++) {
            if (currentButtons[i] !== selectorButtons[i]) {
                log('模型内容变化:', currentButtons[i], '->', selectorButtons[i]);
                return true;
            }
        }

        return false;
    }

    // 观察模型按钮区域的变化
    function observeModelButtons() {
        let container = null;
        for (const selector of config.modelContainerSelectors) {
            container = document.querySelector(selector);
            if (container) break;
        }

        if (!container) {
            log('未找到模型按钮容器');
            return null;
        }

        log('开始观察模型按钮区域:', container);

        const observer = new MutationObserver(() => {
            if (hasModelListChanged()) {
                log('检测到模型按钮变化,更新下拉列表');
                createModernSelector();
            }
        });

        observer.observe(container, {
            childList: true,
            subtree: true,
            attributes: false,
            characterData: false
        });

        return observer;
    }

    // 打开设置面板并切换联网搜索状态
   // 打开设置面板并切换联网搜索状态
async function toggleWebAccess(enabled) {
    log('开始切换联网搜索状态:', enabled);

    // 1. 找到+号按钮并点击
    const plusButton = document.querySelector('div._1ct82yn0 svg')?.closest('div');
    if (!plusButton) {
        log('未找到+号按钮');
        return false;
    }

    log('点击+号按钮');
    plusButton.click();
    await new Promise(resolve => setTimeout(resolve, 500));

    // 2. 找到Web access选项卡并点击
    const webAccessTab = Array.from(document.querySelectorAll('span._1qvgj558'))
                            .find(el => el.textContent.includes('Web access'));
    if (!webAccessTab) {
        log('未找到Web access选项卡');
        return false;
    }

    log('点击Web access选项卡');
    webAccessTab.click();
    await new Promise(resolve => setTimeout(resolve, 500));

    // 3. 找到切换开关并设置状态
    const toggleInput = document.querySelector('input.zvioep1[type="checkbox"]');
    if (!toggleInput) {
        log('未找到Web access切换开关');
        return false;
    }

    // 只有当当前状态与目标状态不同时才切换
    if (toggleInput.checked !== enabled) {
        log('切换Web access开关状态:', toggleInput.checked, '->', enabled);
        toggleInput.click();
        await new Promise(resolve => setTimeout(resolve, 300));
    } else {
        log('Web access开关已经是目标状态:', enabled);
    }

    // 4. 找到保存按钮并点击 - 更新为更精确的选择器
    const saveButton = document.querySelector('button[data-testid="modal-primary-action-button"]');
    if (!saveButton) {
        log('未找到保存按钮');
        return false;
    }

    log('点击保存按钮:', saveButton);
    saveButton.click();
    await new Promise(resolve => setTimeout(resolve, 500));

    log('成功切换联网搜索状态:', enabled);
    return true;
}

    // 创建现代化下拉菜单
    function createModernSelector() {
        // 移除旧的选择器
        const oldSelector = document.getElementById(config.selectorId);
        if (oldSelector) oldSelector.remove();

        const buttons = getAllModelButtons();
        const moreButton = getRealMoreButton();
        if (buttons.length === 0 && !moreButton) {
            log('未找到模型按钮和更多按钮,取消创建选择器');
            return;
        }

        // 确保样式已添加
        addGlobalStyles();

        const currentModel = getCurrentModel();
        log('当前选中的模型:', currentModel);

        // 创建主容器
        const container = document.createElement('div');
        container.id = config.selectorId;

        // 主下拉容器
        const dropdown = document.createElement('div');
        dropdown.className = 'youcom-dropdown';

        // 当前选择显示
        const selected = document.createElement('div');
        selected.className = 'youcom-selected';
        selected.innerHTML = `
            <span class="youcom-model-name">${currentModel}</span>
            <span class="youcom-arrow">▼</span>
        `;

        // 下拉选项容器
        const options = document.createElement('div');
        options.className = 'youcom-options';

        // 添加模型选项
        buttons.forEach(button => {
            const text = cleanModelText(button.textContent);
            if (!text) return;

            const option = document.createElement('div');
            option.textContent = text;
            option.className = 'youcom-option';

            if (text === currentModel) {
                option.classList.add('youcom-option-selected');
            }

            option.addEventListener('click', async (e) => {
                e.stopPropagation();

                log('点击了模型选项:', text);

                // 显示加载状态
                const originalText = option.textContent;
                option.innerHTML = `
                    <span class="youcom-loading"></span>
                    <span style="margin-left:8px">${originalText}</span>
                `;
                option.style.pointerEvents = 'none';

                // 关闭下拉菜单
                options.style.display = 'none';
                selected.querySelector('.youcom-arrow').style.transform = 'rotate(0deg)';

                try {
                    log('点击原始按钮:', button.textContent);
                    button.click();
                    log('等待模型切换完成...');
                    await new Promise(resolve => setTimeout(resolve, config.switchDelay));

                    // 更新显示
                    const newModel = getCurrentModel();
                    log('切换后的模型:', newModel);
                    selected.querySelector('.youcom-model-name').textContent = newModel;

                    // 保存最近使用的模型
                    saveLastUsedModel(newModel);

                    // 更新所有选项状态
                    options.querySelectorAll('.youcom-option').forEach(opt => {
                        opt.classList.remove('youcom-option-selected');
                        if (opt.textContent === newModel) {
                            opt.classList.add('youcom-option-selected');
                        }
                        opt.textContent = opt.textContent.replace('▼', '').trim();
                    });
                } finally {
                    option.style.pointerEvents = '';
                }
            });

            options.appendChild(option);
        });

        // 事件监听
        selected.addEventListener('click', (e) => {
            e.stopPropagation();
            const isVisible = options.style.display === 'block';
            options.style.display = isVisible ? 'none' : 'block';
            selected.querySelector('.youcom-arrow').style.transform =
                isVisible ? 'rotate(0deg)' : 'rotate(180deg)';

            log('切换下拉菜单显示:', !isVisible);
        });

        dropdown.appendChild(selected);
        dropdown.appendChild(options);
        container.appendChild(dropdown);

        // 添加更多按钮到容器
        if (moreButton) {
            const moreBtnContainer = document.createElement('div');
            moreBtnContainer.className = 'youcom-more-btn';

            // 克隆SVG图标
            const svg = moreButton.querySelector('svg')?.cloneNode(true);
            if (svg) {
                moreBtnContainer.appendChild(svg);
            }

            // 添加"More"文本
            const text = document.createElement('span');
            text.textContent = 'More';
            moreBtnContainer.appendChild(text);

            // 添加点击事件
            moreBtnContainer.addEventListener('click', (e) => {
                e.stopPropagation();
                moreButton.click();
            });

            container.appendChild(moreBtnContainer);
        }

        // 添加联网搜索开关
        const webAccessState = getWebAccessState();
        const webToggle = document.createElement('div');
        webToggle.className = 'youcom-web-toggle';
        webToggle.innerHTML = `
            <span class="youcom-web-label">联网搜索</span>
            <label class="youcom-toggle-switch">
                <input type="checkbox" class="youcom-toggle-checkbox" ${webAccessState ? 'checked' : ''}>
                <span class="youcom-toggle-slider"></span>
            </label>
        `;

        // 添加开关事件监听
        const toggleCheckbox = webToggle.querySelector('.youcom-toggle-checkbox');
        toggleCheckbox.addEventListener('change', async (e) => {
            const enabled = e.target.checked;
            log('用户切换联网搜索:', enabled);

            // 显示加载状态
            const originalLabel = webToggle.querySelector('.youcom-web-label');
            const originalText = originalLabel.textContent;
            originalLabel.textContent = '切换中...';
            webToggle.style.pointerEvents = 'none';

            try {
                const success = await toggleWebAccess(enabled);
                if (success) {
                    saveWebAccessState(enabled);
                } else {
                    // 如果失败,恢复原来的状态
                    e.target.checked = !enabled;
                    log('切换联网搜索失败,恢复状态');
                }
            } catch (error) {
                log('切换联网搜索出错:', error);
                e.target.checked = !enabled;
            } finally {
                originalLabel.textContent = originalText;
                webToggle.style.pointerEvents = '';
            }
        });

        // 将联网开关添加到容器最前面
        container.insertBefore(webToggle, container.firstChild);

        document.body.appendChild(container);

        // 全局点击事件用于关闭下拉菜单
        document.addEventListener('click', () => {
            options.style.display = 'none';
            selected.querySelector('.youcom-arrow').style.transform = 'rotate(0deg)';
        });

        log('选择器创建完成');
    }

    // 使用节流函数优化MutationObserver的频繁调用
    function throttle(func, limit) {
        let inThrottle;
        return function() {
            const args = arguments;
            const context = this;
            if (!inThrottle) {
                func.apply(context, args);
                inThrottle = true;
                setTimeout(() => inThrottle = false, limit);
            }
        };
    }

    // 观察URL变化
    function observeUrlChanges() {
        let lastUrl = window.location.href;

        const checkUrlChange = async () => {
            if (lastUrl !== window.location.href) {
                lastUrl = window.location.href;
                log('URL变化:', lastUrl);

                setTimeout(async () => {
                    await autoSelectLastUsedModel();

                    setTimeout(() => {
                        const currentModel = getCurrentModel();
                        log('新对话页面:更新选择器显示为', currentModel);

                        const oldSelector = document.getElementById(config.selectorId);
                        if (oldSelector) oldSelector.remove();
                        createModernSelector();

                        const modelNameElement = document.querySelector(`#${config.selectorId} .youcom-model-name`);
                        if (modelNameElement && currentModel !== '选择模型') {
                            modelNameElement.textContent = currentModel;
                        }
                    }, 1000);
                }, 1500);
            }
        };

        setInterval(checkUrlChange, 1000);
    }

    // 初始化
    async function init() {
        log('脚本初始化');

        // 先尝试自动选择模型
        const modelChanged = await autoSelectLastUsedModel();

        // 如果模型更改了,给一些时间让UI更新
        if (modelChanged) {
            setTimeout(createModernSelector, 800);
        } else {
            createModernSelector();
        }

        // 观察整个DOM的变化(用于初始创建选择器)
        const domObserver = new MutationObserver(throttle(() => {
            if (!document.getElementById(config.selectorId)) {
                log('检测到DOM变化,重新创建选择器');
                createModernSelector();
            }
        }, 500));
        domObserver.observe(document.body, { childList: true, subtree: true });

        // 专门观察模型按钮区域的变化
        observeModelButtons();

        // 观察URL变化以处理新对话
        observeUrlChanges();
        log('所有观察器已设置');
    }

    // 启动脚本
    if (document.readyState === 'loading') {
        log('等待DOMContentLoaded事件');
        document.addEventListener('DOMContentLoaded', init);
    } else {
        log('DOM已加载,1秒后初始化');
        setTimeout(init, 1000);
    }
})();

QingJ © 2025

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