Greasy Fork

Greasy Fork is available in English.

AI Image Description Generator Gimini

使用AI生成网页图片描述

当前为 2024-12-12 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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