// ==UserScript== // @name Gemini 提示词选择器 // @namespace http://tampermonkey.net/ // @version 1.1 // @description 在 Gemini 输入框下方添加图像比例以及风格选择按钮 // @author Claude Opus 4.5; Claude Sonnet 4.5; Gemini 3 Pro; // @match https://gemini.google.com/* // @grant none // ==/UserScript== (function () { 'use strict'; // 可选的图像比例列表 const ASPECT_RATIOS = [ { ratio: '1:1', label: '1:1 正方形, 头像' }, { ratio: '9:16', label: '9:16 手机壁纸, 人像' }, { ratio: '16:9', label: '16:9 桌面壁纸, 风景' }, { ratio: '3:4', label: '3:4 经典比例, 拍照' }, { ratio: '4:3', label: '4:3 文章配图, 插画' }, { ratio: '3:2', label: '3:2 单反相机, 摄影' }, { ratio: '2:3', label: '2:3 社交媒体, 自拍' }, { ratio: '5:4', label: '5:4 艺术画作, 打印' }, { ratio: '4:5', label: '4:5 肖像模式' }, { ratio: '21:9', label: '21:9 电影宽屏' } ]; // 预设风格列表 const DEFAULT_STYLES = [ { name: '写实照片', prompt: '高清写实照片风格,注重细节和真实感' }, { name: '油画', prompt: '古典油画风格,色彩浓郁,笔触可见' }, { name: '水彩', prompt: '清新水彩画风格,色彩柔和透明' }, { name: '赛博朋克', prompt: '赛博朋克风格,霓虹灯光,未来主义' }, { name: '极简主义', prompt: '极简主义设计,简洁线条,留白艺术' }, { name: '动漫', prompt: '日式动漫风格,明亮色彩,精致线条' }, { name: '复古', prompt: '复古怀旧风格,暖色调,胶片质感' }, { name: '科幻', prompt: '科幻未来风格,科技感,宇宙元素' } ]; // 当前选择的比例和风格 let currentRatio = null; let currentStyle = null; // 比例匹配的正则表达式 const ratioPattern = /,\s*(\d+:\d+)比例$/; // LocalStorage 键名 const STORAGE_KEY = 'gemini-custom-styles'; const STORAGE_KEY_FAV = 'gemini-favorite-styles'; // 创建样式 function injectStyles() { const style = document.createElement('style'); style.textContent = ` /* 基础变量定义 - 默认深色模式 */ .ratio-selector-container, .style-selector-container, .custom-style-modal { --g-selector-bg: rgba(255, 255, 255, 0.1); --g-selector-border: rgba(255, 255, 255, 0.2); --g-selector-text: #e3e3e3; --g-selector-hover-bg: rgba(255, 255, 255, 0.15); --g-selector-hover-border: rgba(255, 255, 255, 0.3); --g-selector-active-bg: rgba(138, 180, 248, 0.2); --g-selector-active-border: #8ab4f8; --g-dropdown-bg: #2d2d2d; --g-dropdown-border: rgba(255, 255, 255, 0.2); --g-dropdown-shadow: rgba(0, 0, 0, 0.4); --g-option-hover: rgba(255, 255, 255, 0.1); --g-option-selected-bg: rgba(138, 180, 248, 0.2); --g-option-selected-text: #8ab4f8; --g-option-text: #e3e3e3; --g-scrollbar-thumb: rgba(255, 255, 255, 0.3); --g-scrollbar-thumb-hover: rgba(255, 255, 255, 0.5); --g-btn-add-text: #8ab4f8; --g-btn-add-hover: rgba(138, 180, 248, 0.1); --g-btn-add-border: rgba(255, 255, 255, 0.1); --g-fav-star-inactive: #888888; --g-fav-star-active: #fbbc04; --g-fav-star-hover: #aaaaaa; --g-del-btn-inactive: #888888; --g-del-btn-hover: #ff6b6b; /* Modal Colors */ --g-modal-overlay: rgba(0, 0, 0, 0.7); --g-modal-bg: #2d2d2d; --g-modal-text: #e3e3e3; --g-modal-label: #b8b8b8; --g-modal-input-bg: #1e1e1e; --g-modal-input-border: rgba(255, 255, 255, 0.2); --g-modal-input-text: #e3e3e3; --g-modal-input-focus: #8ab4f8; --g-modal-btn-cancel-bg: rgba(255, 255, 255, 0.1); --g-modal-btn-cancel-text: #e3e3e3; --g-modal-btn-cancel-hover: rgba(255, 255, 255, 0.15); --g-modal-btn-danger-bg: #e53935; --g-modal-btn-danger-hover: #d32f2f; --g-modal-btn-danger-text: #ffffff; } /* 浅色模式变量覆盖 */ .ratio-selector-container.light-theme, .style-selector-container.light-theme, .custom-style-modal.light-theme { --g-selector-bg: rgba(0, 0, 0, 0.05); --g-selector-border: rgba(0, 0, 0, 0.12); --g-selector-text: #444746; --g-selector-hover-bg: rgba(0, 0, 0, 0.08); --g-selector-hover-border: rgba(0, 0, 0, 0.3); --g-selector-active-bg: rgba(11, 87, 208, 0.1); --g-selector-active-border: #0b57d0; --g-dropdown-bg: #ffffff; --g-dropdown-border: rgba(0, 0, 0, 0.12); --g-dropdown-shadow: rgba(0, 0, 0, 0.15); --g-option-hover: rgba(0, 0, 0, 0.05); --g-option-selected-bg: rgba(11, 87, 208, 0.1); --g-option-selected-text: #0b57d0; --g-option-text: #1f1f1f; --g-scrollbar-thumb: rgba(0, 0, 0, 0.2); --g-scrollbar-thumb-hover: rgba(0, 0, 0, 0.3); --g-btn-add-text: #0b57d0; --g-btn-add-hover: rgba(11, 87, 208, 0.1); --g-btn-add-border: rgba(0, 0, 0, 0.1); --g-fav-star-inactive: #9aa0a6; --g-fav-star-active: #fbbc04; --g-fav-star-hover: #5f6368; --g-del-btn-inactive: #9aa0a6; --g-del-btn-hover: #d93025; /* Modal Colors Light */ --g-modal-overlay: rgba(255, 255, 255, 0.6); --g-modal-bg: #ffffff; --g-modal-text: #1f1f1f; --g-modal-label: #444746; --g-modal-input-bg: #f0f4f9; --g-modal-input-border: rgba(0, 0, 0, 0.12); --g-modal-input-text: #1f1f1f; --g-modal-input-focus: #0b57d0; --g-modal-btn-cancel-bg: rgba(0, 0, 0, 0.05); --g-modal-btn-cancel-text: #444746; --g-modal-btn-cancel-hover: rgba(0, 0, 0, 0.1); } .ratio-selector-container { display: inline-flex !important; align-items: center !important; position: relative !important; margin-left: 8px !important; vertical-align: middle !important; z-index: 100 !important; } .style-selector-container { display: inline-flex !important; align-items: center !important; position: relative !important; margin-left: 8px !important; vertical-align: middle !important; z-index: 100 !important; } .ratio-selector-btn { display: flex; align-items: center; justify-content: center; padding: 6px 12px; border: 1px solid var(--g-selector-border); border-radius: 20px; background: var(--g-selector-bg); color: var(--g-selector-text); font-size: 13px; cursor: pointer; transition: all 0.2s ease; font-family: 'Google Sans', sans-serif; white-space: nowrap; } .ratio-selector-btn:hover { background: var(--g-selector-hover-bg); border-color: var(--g-selector-hover-border); } .ratio-selector-btn.active { background: var(--g-selector-active-bg); border-color: var(--g-selector-active-border); color: var(--g-option-selected-text); } .ratio-selector-btn::after { content: '▼'; font-size: 10px; margin-left: 6px; transition: transform 0.2s ease; } .ratio-selector-btn.expanded::after { transform: rotate(180deg); } .ratio-dropdown { position: absolute; left: 0; bottom: 100%; margin-bottom: 4px; background: var(--g-dropdown-bg); border: 1px solid var(--g-dropdown-border); border-radius: 12px; box-shadow: 0 4px 16px var(--g-dropdown-shadow); overflow: hidden; opacity: 0; visibility: hidden; transform: translateY(10px); transition: all 0.2s ease; z-index: 10000; min-width: 160px; } .ratio-dropdown.show { opacity: 1; visibility: visible; transform: translateY(0); } .ratio-dropdown-scroll { max-height: 200px; overflow-y: auto; padding: 4px 0; } .ratio-dropdown-scroll::-webkit-scrollbar { width: 6px; } .ratio-dropdown-scroll::-webkit-scrollbar-track { background: transparent; } .ratio-dropdown-scroll::-webkit-scrollbar-thumb { background: var(--g-scrollbar-thumb); border-radius: 3px; } .ratio-dropdown-scroll::-webkit-scrollbar-thumb:hover { background: var(--g-scrollbar-thumb-hover); } .ratio-option { padding: 10px 16px; color: var(--g-option-text); font-size: 13px; cursor: pointer; transition: background 0.15s ease; white-space: nowrap; font-family: 'Google Sans', sans-serif; } .ratio-option:hover { background: var(--g-option-hover); } .ratio-option.selected { background: var(--g-option-selected-bg); color: var(--g-option-selected-text); } .ratio-option.selected::before { content: '✓ '; } /* 风格选择器样式(复用比例选择器样式) */ .style-selector-container { display: inline-flex; align-items: center; position: relative; margin-left: 8px; vertical-align: middle; } .style-selector-btn { display: flex; align-items: center; justify-content: center; padding: 6px 12px; border: 1px solid var(--g-selector-border); border-radius: 20px; background: var(--g-selector-bg); color: var(--g-selector-text); font-size: 13px; cursor: pointer; transition: all 0.2s ease; font-family: 'Google Sans', sans-serif; white-space: nowrap; } .style-selector-btn:hover { background: var(--g-selector-hover-bg); border-color: var(--g-selector-hover-border); } .style-selector-btn.active { background: var(--g-selector-active-bg); border-color: var(--g-selector-active-border); color: var(--g-option-selected-text); } .style-selector-btn::after { content: '▼'; font-size: 10px; margin-left: 6px; transition: transform 0.2s ease; } .style-selector-btn.expanded::after { transform: rotate(180deg); } .style-dropdown { position: absolute; left: 0; bottom: 100%; margin-bottom: 4px; background: var(--g-dropdown-bg); border: 1px solid var(--g-dropdown-border); border-radius: 12px; box-shadow: 0 4px 16px var(--g-dropdown-shadow); overflow: hidden; opacity: 0; visibility: hidden; transform: translateY(10px); transition: all 0.2s ease; z-index: 10000; min-width: 180px; } .style-dropdown.show { opacity: 1; visibility: visible; transform: translateY(0); } .style-dropdown-scroll { max-height: 250px; overflow-y: auto; padding: 4px 0; } .style-dropdown-scroll::-webkit-scrollbar { width: 6px; } .style-dropdown-scroll::-webkit-scrollbar-track { background: transparent; } .style-dropdown-scroll::-webkit-scrollbar-thumb { background: var(--g-scrollbar-thumb); border-radius: 3px; } .style-dropdown-scroll::-webkit-scrollbar-thumb:hover { background: var(--g-scrollbar-thumb-hover); } .style-option { padding: 8px 16px; color: var(--g-option-text); font-size: 13px; cursor: pointer; transition: background 0.15s ease; font-family: 'Google Sans', sans-serif; display: flex; align-items: center; justify-content: space-between; } .style-option:hover { background: var(--g-option-hover); } .style-option.selected { background: var(--g-option-selected-bg); color: var(--g-option-selected-text); } /* 左侧内容容器 */ .style-option-content { display: flex; align-items: center; flex: 1; } .style-option.selected .style-option-content::before { content: '✓ '; margin-right: 4px; } /* 收藏星星按钮 */ .style-fav-btn { width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; color: var(--g-fav-star-inactive); font-size: 16px; margin-right: 8px; cursor: pointer; transition: all 0.2s ease; user-select: none; } .style-fav-btn:hover { color: var(--g-fav-star-hover); transform: scale(1.1); } .style-fav-btn.active { color: var(--g-fav-star-active); } .style-fav-btn::before { content: '★'; } /* 删除按钮 */ .style-del-btn { width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; color: var(--g-fav-star-inactive); font-size: 14px; margin-left: 4px; cursor: pointer; transition: all 0.2s ease; opacity: 0; border-radius: 4px; flex-shrink: 0; } .style-option:hover .style-del-btn { opacity: 1; } .style-del-btn:hover { color: #ff4444; background: rgba(255, 0, 0, 0.1); } .style-del-btn svg { width: 14px; height: 14px; fill: currentColor; } /* 选中时,左对齐文字,星星在最左边 */ .style-option { padding-left: 8px; /* 给星星留出空间 */ } .add-style-btn { padding: 12px 16px; color: var(--g-btn-add-text); font-size: 14px; font-weight: 500; cursor: pointer; transition: background 0.15s ease; border-bottom: 1px solid var(--g-btn-add-border); text-align: center; font-family: 'Google Sans', sans-serif; } .add-style-btn:hover { background: var(--g-btn-add-hover); } .add-style-btn::before { content: '+ '; font-size: 16px; } /* 弹窗样式 */ .custom-style-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: var(--g-modal-overlay); display: none; align-items: center; justify-content: center; z-index: 20000; } .custom-style-modal.show { display: flex; } .modal-content { background: var(--g-modal-bg); border-radius: 16px; padding: 24px; width: 90%; max-width: 450px; box-shadow: 0 8px 32px var(--g-dropdown-shadow); animation: modalSlideIn 0.3s ease; } @keyframes modalSlideIn { from { opacity: 0; transform: translateY(-30px); } to { opacity: 1; transform: translateY(0); } } .modal-header { font-size: 18px; font-weight: 500; color: var(--g-modal-text); margin-bottom: 20px; font-family: 'Google Sans', sans-serif; } .modal-form-group { margin-bottom: 16px; } .modal-label { display: block; font-size: 13px; color: var(--g-modal-label); margin-bottom: 8px; font-family: 'Google Sans', sans-serif; } .modal-input { width: 100%; padding: 10px 12px; font-size: 14px; background: var(--g-modal-input-bg); border: 1px solid var(--g-modal-input-border); border-radius: 8px; color: var(--g-modal-input-text); outline: none; transition: border-color 0.2s ease; font-family: 'Google Sans', sans-serif; box-sizing: border-box; } .modal-input:focus { border-color: var(--g-modal-input-focus); } .modal-textarea { width: 100%; min-height: 80px; padding: 10px 12px; font-size: 14px; background: var(--g-modal-input-bg); border: 1px solid var(--g-modal-input-border); border-radius: 8px; color: var(--g-modal-input-text); outline: none; transition: border-color 0.2s ease; resize: vertical; font-family: 'Google Sans', sans-serif; box-sizing: border-box; } .modal-textarea:focus { border-color: var(--g-modal-input-focus); } .modal-actions { display: flex; justify-content: flex-end; gap: 12px; margin-top: 24px; } .modal-btn { padding: 8px 20px; font-size: 14px; border: none; border-radius: 20px; cursor: pointer; transition: all 0.2s ease; font-family: 'Google Sans', sans-serif; font-weight: 500; } .modal-btn-cancel { background: var(--g-modal-btn-cancel-bg); color: var(--g-modal-btn-cancel-text); } .modal-btn-cancel:hover { background: var(--g-modal-btn-cancel-hover); } .modal-btn-confirm { background: #8ab4f8; color: #1e1e1e; } .modal-btn-confirm:hover { background: #a8c7fa; } `; document.head.appendChild(style); } // ========== LocalStorage 工具函数 ========== // 加载自定义风格 function loadCustomStyles() { try { const saved = localStorage.getItem(STORAGE_KEY); return saved ? JSON.parse(saved) : []; } catch (e) { console.error('加载自定义风格失败:', e); return []; } } // 保存自定义风格 function saveCustomStyles(styles) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(styles)); } catch (e) { console.error('保存自定义风格失败:', e); } } // 获取所有风格(预设 + 自定义) function getAllStyles() { const customStyles = loadCustomStyles(); return [...DEFAULT_STYLES, ...customStyles]; } // 添加新风格 function addCustomStyle(name, prompt) { const customStyles = loadCustomStyles(); customStyles.push({ name, prompt }); saveCustomStyles(customStyles); } // 删除自定义风格 function deleteCustomStyle(name) { let customStyles = loadCustomStyles(); customStyles = customStyles.filter(s => s.name !== name); saveCustomStyles(customStyles); // 同时从收藏中移除 let favorites = loadFavorites(); if (favorites.includes(name)) { favorites = favorites.filter(f => f !== name); saveFavorites(favorites); } } // ========== 收藏功能工具函数 ========== // 加载收藏列表 function loadFavorites() { try { const saved = localStorage.getItem(STORAGE_KEY_FAV); return saved ? JSON.parse(saved) : []; } catch (e) { console.error('加载收藏列表失败:', e); return []; } } // 保存收藏列表 function saveFavorites(favorites) { try { localStorage.setItem(STORAGE_KEY_FAV, JSON.stringify(favorites)); } catch (e) { console.error('保存收藏列表失败:', e); } } // 切换收藏状态 function toggleFavorite(styleName) { let favorites = loadFavorites(); if (favorites.includes(styleName)) { favorites = favorites.filter(name => name !== styleName); } else { favorites.push(styleName); } saveFavorites(favorites); return favorites; // 返回最新列表 } // ========== 输入框操作函数 ========== // 获取输入框元素 function getInputElement() { return document.querySelector('.ql-editor.textarea'); } // ... (getInputText 到 updateStyleInInput 函数保持不变) ... function getInputText() { const editor = getInputElement(); if (!editor) return ''; return editor.innerText.trim(); } function setInputText(text) { const editor = getInputElement(); if (!editor) return; // 清空并设置新内容(使用 DOM API 避免 TrustedHTML 问题) while (editor.firstChild) { editor.removeChild(editor.firstChild); } const p = document.createElement('p'); p.textContent = text; editor.appendChild(p); // 触发输入事件以更新 Gemini 的内部状态 const inputEvent = new Event('input', { bubbles: true, cancelable: true }); editor.dispatchEvent(inputEvent); // 将光标移动到末尾 const range = document.createRange(); const sel = window.getSelection(); range.selectNodeContents(editor); range.collapse(false); sel.removeAllRanges(); sel.addRange(range); } function updateRatioInInput(newRatio) { let text = getInputText(); if (ratioPattern.test(text)) { // 替换已有的比例 text = text.replace(ratioPattern, `, ${newRatio}比例`); } else { // 添加新的比例 if (text) { text = text + `, ${newRatio}比例`; } else { text = `${newRatio}比例`; } } setInputText(text); currentRatio = newRatio; } function updateStyleInInput(styleName, stylePrompt) { let text = getInputText(); // 直接在末尾添加风格提示词 if (text) { text = text + `, ${stylePrompt}`; } else { text = stylePrompt; } setInputText(text); currentStyle = styleName; } // ... (createRatioSelector 到 createCustomStyleModal 函数保持不变) ... function createRatioSelector() { // ... (原函数内容不变,这里只是占位,下面会完整替换 style selector 部分) const container = document.createElement('div'); container.className = 'ratio-selector-container'; const btn = document.createElement('button'); btn.className = 'ratio-selector-btn'; btn.textContent = '比例'; btn.type = 'button'; const dropdown = document.createElement('div'); dropdown.className = 'ratio-dropdown'; const scrollContainer = document.createElement('div'); scrollContainer.className = 'ratio-dropdown-scroll'; ASPECT_RATIOS.forEach(item => { const option = document.createElement('div'); option.className = 'ratio-option'; option.textContent = item.label; option.dataset.ratio = item.ratio; option.addEventListener('click', (e) => { e.stopPropagation(); scrollContainer.querySelectorAll('.ratio-option').forEach(opt => { opt.classList.remove('selected'); }); option.classList.add('selected'); btn.textContent = item.ratio; btn.classList.add('active'); updateRatioInInput(item.ratio); dropdown.classList.remove('show'); btn.classList.remove('expanded'); }); scrollContainer.appendChild(option); }); dropdown.appendChild(scrollContainer); btn.addEventListener('click', (e) => { e.stopPropagation(); const isExpanded = dropdown.classList.contains('show'); if (isExpanded) { dropdown.classList.remove('show'); btn.classList.remove('expanded'); } else { dropdown.classList.add('show'); btn.classList.add('expanded'); } }); document.addEventListener('click', () => { dropdown.classList.remove('show'); btn.classList.remove('expanded'); }); container.appendChild(btn); container.appendChild(dropdown); return container; } function createCustomStyleModal(onStyleAdded) { const modal = document.createElement('div'); modal.className = 'custom-style-modal'; const modalContent = document.createElement('div'); modalContent.className = 'modal-content'; // 使用 DOM API 构建弹窗内容(避免 TrustedHTML 问题) const header = document.createElement('div'); header.className = 'modal-header'; header.textContent = '添加自定义风格'; // 风格名称输入 const formGroup1 = document.createElement('div'); formGroup1.className = 'modal-form-group'; const label1 = document.createElement('label'); label1.className = 'modal-label'; label1.textContent = '风格名称'; const nameInput = document.createElement('input'); nameInput.type = 'text'; nameInput.className = 'modal-input'; nameInput.id = 'style-name-input'; nameInput.placeholder = '例如:蒸汽波'; formGroup1.appendChild(label1); formGroup1.appendChild(nameInput); // 风格提示词输入 const formGroup2 = document.createElement('div'); formGroup2.className = 'modal-form-group'; const label2 = document.createElement('label'); label2.className = 'modal-label'; label2.textContent = '风格提示词'; const promptInput = document.createElement('textarea'); promptInput.className = 'modal-textarea'; promptInput.id = 'style-prompt-input'; promptInput.placeholder = '例如:蒸汽波美学,粉紫色调,复古电脑图形,80年代风格'; formGroup2.appendChild(label2); formGroup2.appendChild(promptInput); // 按钮区域 const actions = document.createElement('div'); actions.className = 'modal-actions'; const cancelBtn = document.createElement('button'); cancelBtn.className = 'modal-btn modal-btn-cancel'; cancelBtn.textContent = '取消'; const confirmBtn = document.createElement('button'); confirmBtn.className = 'modal-btn modal-btn-confirm'; confirmBtn.textContent = '确认'; actions.appendChild(cancelBtn); actions.appendChild(confirmBtn); // 组装弹窗 modalContent.appendChild(header); modalContent.appendChild(formGroup1); modalContent.appendChild(formGroup2); modalContent.appendChild(actions); modal.appendChild(modalContent); const closeModal = () => { modal.classList.remove('show'); nameInput.value = ''; promptInput.value = ''; }; modal.addEventListener('click', (e) => { if (e.target === modal) { closeModal(); } }); cancelBtn.addEventListener('click', closeModal); confirmBtn.addEventListener('click', () => { const name = nameInput.value.trim(); const prompt = promptInput.value.trim(); if (!name || !prompt) { alert('请填写完整的风格名称和提示词'); return; } addCustomStyle(name, prompt); if (onStyleAdded) { onStyleAdded(); } closeModal(); }); const handleEnter = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); confirmBtn.click(); } }; nameInput.addEventListener('keydown', handleEnter); return modal; } // 创建风格选择器 function createStyleSelector() { const container = document.createElement('div'); container.className = 'style-selector-container'; // 创建主按钮 const btn = document.createElement('button'); btn.className = 'style-selector-btn'; btn.textContent = '风格'; btn.type = 'button'; // 创建下拉菜单 const dropdown = document.createElement('div'); dropdown.className = 'style-dropdown'; const scrollContainer = document.createElement('div'); scrollContainer.className = 'style-dropdown-scroll'; // 渲染风格选项 const renderStyleOptions = () => { // 清空容器(使用 DOM API 避免 TrustedHTML 问题) while (scrollContainer.firstChild) { scrollContainer.removeChild(scrollContainer.firstChild); } const favorites = loadFavorites(); // 添加"+"按钮 const addBtn = document.createElement('div'); addBtn.className = 'add-style-btn'; addBtn.textContent = '添加自定义风格'; addBtn.addEventListener('click', (e) => { e.stopPropagation(); modal.classList.add('show'); dropdown.classList.remove('show'); btn.classList.remove('expanded'); }); scrollContainer.appendChild(addBtn); // 获取所有风格并排序 let allStyles = getAllStyles(); // 排序:收藏的在顶部,其他按原顺序 allStyles.sort((a, b) => { const aFav = favorites.includes(a.name); const bFav = favorites.includes(b.name); if (aFav && !bFav) return -1; if (!aFav && bFav) return 1; return 0; // 保持原有相对顺序 }); allStyles.forEach(style => { const isFav = favorites.includes(style.name); // 判断是否为自定义风格(不在默认列表里的) const isCustom = !DEFAULT_STYLES.some(ds => ds.name === style.name); const option = document.createElement('div'); option.className = 'style-option'; // 收藏星标 const favBtn = document.createElement('div'); favBtn.className = `style-fav-btn ${isFav ? 'active' : ''}`; favBtn.title = isFav ? '取消置顶' : '收藏并置顶'; favBtn.addEventListener('click', (e) => { e.stopPropagation(); toggleFavorite(style.name); renderStyleOptions(); // 重新渲染列表(会自动重排序) }); // 选项内容容器 const content = document.createElement('div'); content.className = 'style-option-content'; content.textContent = style.name; // 组装 option.appendChild(favBtn); option.appendChild(content); // 如果是自定义风格,添加删除按钮 if (isCustom) { const delBtn = document.createElement('div'); delBtn.className = 'style-del-btn'; delBtn.title = '删除此自定义风格'; // 使用文本图标(避免 TrustedHTML 问题) delBtn.textContent = '🗑'; delBtn.addEventListener('click', (e) => { e.stopPropagation(); // 弹出原生确认框 if (confirm(`确定要删除自定义风格 "${style.name}" 吗?`)) { deleteCustomStyle(style.name); renderStyleOptions(); // 删除后重新渲染 } }); option.appendChild(delBtn); } option.dataset.styleName = style.name; option.dataset.stylePrompt = style.prompt; option.addEventListener('click', (e) => { e.stopPropagation(); // 更新选中状态 scrollContainer.querySelectorAll('.style-option').forEach(opt => { opt.classList.remove('selected'); }); option.classList.add('selected'); // 更新按钮文本 btn.textContent = style.name; btn.classList.add('active'); // 更新输入框 updateStyleInInput(style.name, style.prompt); // 关闭下拉菜单 dropdown.classList.remove('show'); btn.classList.remove('expanded'); }); scrollContainer.appendChild(option); }); }; // 初始化渲染 renderStyleOptions(); dropdown.appendChild(scrollContainer); // 创建弹窗 const modal = createCustomStyleModal(() => { renderStyleOptions(); // 重新渲染选项列表 }); document.body.appendChild(modal); // 按钮点击事件 btn.addEventListener('click', (e) => { e.stopPropagation(); const isExpanded = dropdown.classList.contains('show'); // 关闭所有其他下拉菜单 document.querySelectorAll('.ratio-dropdown.show, .style-dropdown.show').forEach(d => { d.classList.remove('show'); }); document.querySelectorAll('.ratio-selector-btn.expanded, .style-selector-btn.expanded').forEach(b => { b.classList.remove('expanded'); }); if (!isExpanded) { dropdown.classList.add('show'); btn.classList.add('expanded'); } }); // 点击外部关闭下拉菜单 document.addEventListener('click', () => { dropdown.classList.remove('show'); btn.classList.remove('expanded'); }); container.appendChild(btn); container.appendChild(dropdown); return container; } // 查找并插入选择器 function insertSelector() { // 查找所有工具栏区域 const leadingActionsWrappers = document.querySelectorAll('.leading-actions-wrapper'); if (leadingActionsWrappers.length === 0) { return false; } let inserted = false; leadingActionsWrappers.forEach((wrapper) => { // 检查该 wrapper 内是否已存在我们的选择器 if (wrapper.querySelector('.ratio-selector-container')) { return; // 已存在,跳过 } // 检查 wrapper 是否可见且在文档中 if (!wrapper.offsetParent && wrapper.style.display !== 'flex') { return; // 不可见的跳过 } const ratioSelector = createRatioSelector(); const styleSelector = createStyleSelector(); // 使用 prepend 方式插入到开头,或找到合适位置 // 在 toolbox-drawer 之后插入(如果存在) const toolboxDrawer = wrapper.querySelector('toolbox-drawer'); if (toolboxDrawer && toolboxDrawer.nextSibling) { wrapper.insertBefore(ratioSelector, toolboxDrawer.nextSibling); wrapper.insertBefore(styleSelector, ratioSelector.nextSibling); } else { wrapper.appendChild(ratioSelector); wrapper.appendChild(styleSelector); } console.log('[Gemini 提示词选择器] 成功插入选择器'); inserted = true; }); return inserted; } // ========== 主题自动检测 ========== // 检查并更新主题 function updateTheme() { // 获取 body 背景颜色并计算亮度 const bodyColor = window.getComputedStyle(document.body).backgroundColor; const rgb = bodyColor.match(/\d+/g); if (!rgb) return; // 计算亮度 (YIQ公式) const brightness = (parseInt(rgb[0]) * 299 + parseInt(rgb[1]) * 587 + parseInt(rgb[2]) * 114) / 1000; const isLight = brightness > 128; // 亮度大于128认为是浅色模式 const containers = document.querySelectorAll('.ratio-selector-container, .style-selector-container, .custom-style-modal'); containers.forEach(el => { if (isLight) { el.classList.add('light-theme'); } else { el.classList.remove('light-theme'); } }); } // 初始化 function init() { injectStyles(); // 尝试立即插入 if (!insertSelector()) { const observer = new MutationObserver((mutations, obs) => { if (insertSelector()) { updateTheme(); // 插入后立即更新主题 } }); observer.observe(document.body, { childList: true, subtree: true }); } else { updateTheme(); } // 观察 body 属性变化(深浅模式切换通常会改变 body 属性或类名,导致重绘) const themeObserver = new MutationObserver(() => { updateTheme(); }); themeObserver.observe(document.body, { attributes: true, attributeFilter: ['class', 'style', 'data-theme'] }); // 定期检查确保选择器存在和主题正确(处理 SPA 页面切换) setInterval(() => { insertSelector(); updateTheme(); }, 1000); } // 等待页面加载完成 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();