您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
使用AI生成网页图片描述
当前为
// ==UserScript== // @name AI Image Description Generator Gimini // @namespace http://tampermonkey.net/ // @version 1.0 // @description 使用AI生成网页图片描述 // @author AlphaCat // @match *://*/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @license MIT // ==/UserScript== (function() { 'use strict'; // 全局变量 let isSelectionMode = false; // 定义默认提示词 const DEFAULT_PROMPT = "Describe the image from the perspective of someone with only a kindergarten education. If it is a realistic photo, analyze the elements of aperture, focal length, and shutter separately from the perspective of a professional photographer and describe the subject matter of the photo."; // 添加样式 GM_addStyle(` .ai-config-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); z-index: 10000; min-width: 500px; height: auto; } .ai-config-modal h3 { margin: 0 0 15px 0; font-size: 14px; font-weight: bold; color: #333; } .ai-config-modal label { display: inline-block; font-size: 12px; font-weight: bold; color: #333; margin: 0; line-height: normal; height: auto; } .ai-config-modal .input-wrapper { position: relative; display: flex; align-items: center; } .ai-config-modal input { display: block; width: 100%; padding: 2px 24px 2px 2px; margin: 2px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; line-height: normal; height: auto; box-sizing: border-box; } .ai-config-modal .input-icon { position: absolute; right: 4px; width: 16px; height: 16px; cursor: pointer; display: flex; align-items: center; justify-content: center; color: #666; font-size: 12px; user-select: none; } .ai-config-modal .clear-icon { right: 24px; } .ai-config-modal .toggle-password { right: 4px; } .ai-config-modal .input-icon:hover { color: #333; } .ai-config-modal .input-group { margin-bottom: 12px; height: auto; display: flex; flex-direction: column; } .ai-config-modal .button-row { display: flex; gap: 10px; align-items: center; margin-top: 5px; } .ai-config-modal .check-button { padding: 4px 8px; border: none; border-radius: 4px; background: #007bff; color: white; cursor: pointer; font-size: 12px; } .ai-config-modal .check-button:hover { background: #0056b3; } .ai-config-modal .check-button:disabled { background: #cccccc; cursor: not-allowed; } .ai-config-modal select { width: 100%; padding: 4px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; margin-top: 2px; } .ai-config-modal .status-text { font-size: 12px; margin-left: 10px; } .ai-config-modal .status-success { color: #28a745; } .ai-config-modal .status-error { color: #dc3545; } .ai-config-modal button { margin: 10px 5px; padding: 8px 15px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; } .ai-config-modal button#ai-save-config { background: #4CAF50; color: white; } .ai-config-modal button#ai-cancel-config { background: #dc3545; color: white; } .ai-config-modal button:hover { opacity: 0.9; } .ai-floating-btn { position: fixed; width: 32px; height: 32px; background: #4CAF50; color: white; border-radius: 50%; cursor: move; z-index: 9999; box-shadow: 0 2px 5px rgba(0,0,0,0.2); display: flex; align-items: center; justify-content: center; user-select: none; transition: background-color 0.3s; } .ai-floating-btn:hover { background: #45a049; } .ai-floating-btn svg { width: 20px; height: 20px; fill: white; } .ai-menu { position: absolute; background: white; border-radius: 5px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); padding: 8px; z-index: 10000; display: flex; gap: 8px; } .ai-menu-item { width: 32px; height: 32px; padding: 6px; cursor: pointer; border-radius: 50%; display: flex; align-items: center; justify-content: center; transition: background-color 0.3s; } .ai-menu-item:hover { background: #f5f5f5; } .ai-menu-item svg { width: 20px; height: 20px; fill: #666; } .ai-menu-item:hover svg { fill: #4CAF50; } .ai-image-options { display: flex; flex-direction: column; gap: 10px; margin: 15px 0; } .ai-image-options button { padding: 8px 15px; border: none; border-radius: 4px; background: #4CAF50; color: white; cursor: pointer; transition: background-color 0.3s; font-size: 14px; } .ai-image-options button:hover { background: #45a049; } #ai-cancel { background: #dc3545; color: white; } #ai-cancel:hover { opacity: 0.9; } .ai-toast { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); padding: 10px 20px; background: rgba(0, 0, 0, 0.8); color: white; border-radius: 4px; font-size: 14px; z-index: 10000; animation: fadeInOut 3s ease; pointer-events: none; white-space: pre-line; text-align: center; max-width: 80%; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); } @keyframes fadeInOut { 0% { opacity: 0; transform: translate(-50%, 10px); } 10% { opacity: 1; transform: translate(-50%, 0); } 90% { opacity: 1; transform: translate(-50%, 0); } 100% { opacity: 0; transform: translate(-50%, -10px); } } .ai-config-modal .button-group { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; } .ai-config-modal .button-group button { padding: 6px 16px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background-color 0.2s; } .ai-config-modal .save-button { background: #007bff; color: white; } .ai-config-modal .save-button:hover { background: #0056b3; } .ai-config-modal .save-button:disabled { background: #cccccc; cursor: not-allowed; } .ai-config-modal .cancel-button { background: #f8f9fa; color: #333; } .ai-config-modal .cancel-button:hover { background: #e2e6ea; } .ai-selecting-image { cursor: crosshair !important; } .ai-selecting-image * { cursor: crosshair !important; } .ai-image-description { position: fixed; background: rgba(0, 0, 0, 0.8); color: white; padding: 8px 12px; border-radius: 4px; font-size: 14px; line-height: 1.4; max-width: 300px; text-align: center; word-wrap: break-word; z-index: 10000; pointer-events: none; animation: fadeIn 0.3s ease; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .ai-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 9999; } .ai-result-modal { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); position: relative; min-width: 300px; max-width: 1000px; max-height: 540px; overflow-y: auto; width: 90%; } .ai-result-modal h3 { margin: 0 0 10px 0; font-size: 14px; color: #333; } .ai-result-modal .description-code { background: #1e1e1e; color: #ffffff; padding: 12px; border-radius: 4px; margin: 5px 0; cursor: pointer; white-space: pre-wrap; word-wrap: break-word; font-family: monospace; border: 1px solid #333; position: relative; max-height: 500px; overflow-y: auto; font-size: 12px; line-height: 1.4; } .ai-result-modal .description-code * { color: #ffffff !important; } .ai-result-modal .description-code code { color: #ffffff; display: block; width: 100%; } .ai-result-modal .description-code:hover { background: #2d2d2d; color: #ffffff; } .ai-result-modal .copy-hint { font-size: 11px; color: #666; text-align: center; margin: 2px 0; } .ai-result-modal .close-button { position: absolute; top: 8px; right: 8px; background: none; border: none; font-size: 18px; cursor: pointer; color: #666; padding: 2px 6px; line-height: 1; } .ai-result-modal .close-button:hover { color: #333; } .ai-selection-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 9998; cursor: crosshair; pointer-events: none; } .ai-selecting-image img { position: relative; z-index: 9999; cursor: pointer !important; transition: outline 0.2s ease; } .ai-selecting-image img:hover { outline: 2px solid white; outline-offset: 2px; } /* 移动端样式优化 */ @media (max-width: 768px) { .ai-floating-btn { width: 40px; height: 40px; touch-action: none; } .ai-floating-btn svg { width: 24px; height: 24px; } .ai-config-modal { width: 90%; min-width: auto; max-width: 400px; padding: 15px; margin: 10px; box-sizing: border-box; } .ai-config-modal .button-group { margin-top: 15px; flex-direction: row; justify-content: space-between; gap: 10px; } .ai-config-modal .button-group button { flex: 1; min-height: 44px; font-size: 16px; padding: 10px; margin: 0; } .ai-result-modal { width: 95%; min-width: auto; max-width: 90%; margin: 10px; padding: 15px; } .ai-modal-overlay { padding: 10px; box-sizing: border-box; } .ai-config-modal button, .ai-config-modal .input-icon, .ai-config-modal select, .ai-config-modal input { min-height: 44px; padding: 10px; font-size: 16px; } .ai-config-modal textarea { min-height: 100px; font-size: 16px; padding: 10px; } .ai-config-modal .input-icon { width: 44px; height: 44px; font-size: 20px; } .ai-config-modal { max-height: 90vh; overflow-y: auto; -webkit-overflow-scrolling: touch; } } `); // 显示toast提示 function showToast(message, duration = 3000) { const toast = document.createElement('div'); toast.className = 'ai-toast'; toast.textContent = message; document.body.appendChild(toast); setTimeout(() => { toast.remove(); }, duration); } // 进入图片选择模式 function enterImageSelectionMode() { if(isSelectionMode) return; isSelectionMode = true; const floatingBtn = document.querySelector('.ai-floating-btn'); if(floatingBtn) { floatingBtn.style.display = 'none'; } const overlay = document.createElement('div'); overlay.className = 'ai-selection-overlay'; document.body.appendChild(overlay); document.body.classList.add('ai-selecting-image'); const clickHandler = async function(e) { if (!isSelectionMode) return; if (e.target.tagName === 'IMG') { e.preventDefault(); e.stopPropagation(); showToast('图片选择功能待实现'); exitImageSelectionMode(); } }; document.addEventListener('click', clickHandler, true); const escHandler = (e) => { if (e.key === 'Escape') { exitImageSelectionMode(); } }; document.addEventListener('keydown', escHandler); window._imageSelectionHandlers = { click: clickHandler, keydown: escHandler }; } // 退出图片选择模式 function exitImageSelectionMode() { isSelectionMode = false; const floatingBtn = document.querySelector('.ai-floating-btn'); if(floatingBtn) { floatingBtn.style.display = 'flex'; } const overlay = document.querySelector('.ai-selection-overlay'); if (overlay) { overlay.remove(); } document.body.classList.remove('ai-selecting-image'); if (window._imageSelectionHandlers) { document.removeEventListener('click', window._imageSelectionHandlers.click, true); document.removeEventListener('keydown', window._imageSelectionHandlers.keydown); window._imageSelectionHandlers = null; } } // 创建配置界面 function createConfigUI() { const existingModal = document.querySelector('.ai-modal-overlay'); if (existingModal) { existingModal.remove(); } const overlay = document.createElement('div'); overlay.className = 'ai-modal-overlay'; const modal = document.createElement('div'); modal.className = 'ai-config-modal'; modal.innerHTML = ` <h3>AI图像描述配置</h3> <div class="input-group"> <label>API Endpoint:</label> <div class="input-wrapper"> <input type="text" id="ai-endpoint" placeholder="https://api.openai.com" value="${GM_getValue('apiEndpoint', '')}"> <span class="input-icon clear-icon" title="清空">✕</span> </div> </div> <div class="input-group"> <label>API Key (每行一个):</label> <div class="input-wrapper"> <textarea id="ai-apikey" rows="5" style="width: 100%; resize: vertical;">${GM_getValue('apiKey', '')}</textarea> <span class="input-icon clear-icon" title="清空">✕</span> </div> <div class="button-row"> <button class="check-button" id="check-api">检测可用性</button> </div> </div> <div class="input-group"> <label>可用模型:</label> <select id="ai-model"> <option value="">请先检测API可用性</option> </select> </div> <div class="input-group"> <label>提示词:</label> <div class="input-wrapper"> <textarea id="ai-prompt" rows="4" style="width: 100%; resize: vertical;">${GM_getValue('customPrompt', DEFAULT_PROMPT)}</textarea> <span class="input-icon clear-icon" title="重置为默认值">↺</span> </div> </div> <div class="button-group"> <button type="button" class="cancel-button" id="ai-cancel-config">取消</button> <button type="button" class="save-button" id="ai-save-config">保存</button> </div> `; overlay.appendChild(modal); document.body.appendChild(overlay); // 添加清空按钮事件 const clearButtons = modal.querySelectorAll('.clear-icon'); clearButtons.forEach(button => { button.addEventListener('click', function(e) { const input = this.parentElement.querySelector('input, textarea'); if (input) { if (input.id === 'ai-prompt') { input.value = DEFAULT_PROMPT; } else { input.value = ''; } input.focus(); } }); }); // 检测API可用性按钮点击事件 const checkButton = modal.querySelector('#check-api'); if (checkButton) { checkButton.addEventListener('click', function() { showToast('API检测功能待实现'); }); } // 保存配置 const saveButton = modal.querySelector('#ai-save-config'); if (saveButton) { saveButton.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); const endpoint = modal.querySelector('#ai-endpoint')?.value?.trim() || ''; const apiKeys = modal.querySelector('#ai-apikey')?.value?.trim() || ''; const selectedModel = modal.querySelector('#ai-model')?.value || ''; const customPrompt = modal.querySelector('#ai-prompt')?.value?.trim() || DEFAULT_PROMPT; if (!endpoint || !apiKeys) { showToast('请填写API Endpoint和至少一个API Key'); return; } GM_setValue('apiEndpoint', endpoint); GM_setValue('apiKey', apiKeys); GM_setValue('selectedModel', selectedModel); GM_setValue('customPrompt', customPrompt); showToast('配置已保存'); overlay.remove(); }); } // 取消配置 const cancelButton = modal.querySelector('#ai-cancel-config'); if (cancelButton) { cancelButton.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); overlay.remove(); }); } // 点击遮罩层关闭 overlay.addEventListener('click', function(e) { if (e.target === overlay) { overlay.remove(); } }); // 阻止模态框内的点击事件冒泡 modal.addEventListener('click', function(e) { e.stopPropagation(); }); } // 创建悬浮按钮 function createFloatingButton() { const btn = document.createElement('div'); btn.className = 'ai-floating-btn'; btn.innerHTML = ` <svg viewBox="0 0 24 24"> <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm0-14c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm0 10c-2.21 0-4-1.79-4-4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"/> </svg> `; const savedPos = JSON.parse(GM_getValue('btnPosition', '{"x": 20, "y": 20}')); btn.style.left = (savedPos.x || 20) + 'px'; btn.style.top = (savedPos.y || 20) + 'px'; btn.style.right = 'auto'; btn.style.bottom = 'auto'; let isDragging = false; let hasMoved = false; let startX, startY; let initialLeft, initialTop; let longPressTimer; let touchStartTime; // 触屏事件处理 btn.addEventListener('touchstart', function(e) { e.preventDefault(); touchStartTime = Date.now(); longPressTimer = setTimeout(() => { exitImageSelectionMode(); createConfigUI(); }, 500); const touch = e.touches[0]; startX = touch.clientX; startY = touch.clientY; const rect = btn.getBoundingClientRect(); initialLeft = rect.left; initialTop = rect.top; }); btn.addEventListener('touchmove', function(e) { e.preventDefault(); clearTimeout(longPressTimer); const touch = e.touches[0]; const deltaX = touch.clientX - startX; const deltaY = touch.clientY - startY; if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) { hasMoved = true; } const newLeft = Math.max(0, Math.min(window.innerWidth - btn.offsetWidth, initialLeft + deltaX)); const newTop = Math.max(0, Math.min(window.innerHeight - btn.offsetHeight, initialTop + deltaY)); btn.style.left = newLeft + 'px'; btn.style.top = newTop + 'px'; }); btn.addEventListener('touchend', function(e) { e.preventDefault(); clearTimeout(longPressTimer); const touchDuration = Date.now() - touchStartTime; if (!hasMoved && touchDuration < 500) { enterImageSelectionMode(); } if (hasMoved) { const rect = btn.getBoundingClientRect(); GM_setValue('btnPosition', JSON.stringify({ x: rect.left, y: rect.top })); } hasMoved = false; }); // 鼠标事件处理 btn.addEventListener('click', function(e) { if (e.button === 0 && !hasMoved) { enterImageSelectionMode(); e.stopPropagation(); } hasMoved = false; }); btn.addEventListener('contextmenu', function(e) { e.preventDefault(); exitImageSelectionMode(); createConfigUI(); }); // 拖拽相关事件 function dragStart(e) { if (e.target === btn || btn.contains(e.target)) { isDragging = true; hasMoved = false; const rect = btn.getBoundingClientRect(); startX = e.clientX; startY = e.clientY; initialLeft = rect.left; initialTop = rect.top; e.preventDefault(); } } function drag(e) { if (isDragging) { e.preventDefault(); const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) { hasMoved = true; } const newLeft = Math.max(0, Math.min(window.innerWidth - btn.offsetWidth, initialLeft + deltaX)); const newTop = Math.max(0, Math.min(window.innerHeight - btn.offsetHeight, initialTop + deltaY)); btn.style.left = newLeft + 'px'; btn.style.top = newTop + 'px'; } } function dragEnd(e) { if (isDragging) { isDragging = false; const rect = btn.getBoundingClientRect(); GM_setValue('btnPosition', JSON.stringify({ x: rect.left, y: rect.top })); } } btn.addEventListener('mousedown', dragStart); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', dragEnd); document.body.appendChild(btn); return btn; } // 初始化 function initialize() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { createFloatingButton(); }); } else { createFloatingButton(); } } // 启动脚本 initialize(); })();