// ==UserScript== // @name Gemini 提示词选择器 // @namespace http://tampermonkey.net/ // @version 1.0 // @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; align-items: center; position: relative; margin-left: 8px; vertical-align: middle; } /* ... (省略中间未变部分) ... */ /* 收藏星星按钮 */ .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-del-btn-inactive); font-size: 14px; margin-left: 8px; cursor: pointer; transition: all 0.2s ease; opacity: 0; /* 默认隐藏 */ border-radius: 4px; } .style-option:hover .style-del-btn { opacity: 1; /* 悬停显示 */ } .style-del-btn:hover { color: var(--g-del-btn-hover); background: rgba(255, 0, 0, 0.1); } .style-del-btn::before { content: '🗑️'; /* 或者使用 unicode 字符或图标字体 */ font-size: 12px; /* 为了兼容性,这里使用简单的emoji或字符,也可以用SVG */ } /* 使用 SVG 图标作为删除按钮内容会更好看 */ .style-del-btn svg { width: 14px; height: 14px; fill: currentColor; } /* 选中时,左对齐文字,星星在最左边 */ .style-option { padding-left: 8px; /* 给星星留出空间 */ } .add-style-btn { align-items: center; position: relative; margin-left: 8px; vertical-align: middle; } .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-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; // 清空并设置新内容 editor.innerHTML = ''; 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'; modalContent.innerHTML = `