// ==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);
}
})();