GLIF AI Batch Generator

AI-powered batch image generation for GLIF

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         GLIF AI Batch Generator
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  AI-powered batch image generation for GLIF
// @author       i12bp8
// @match        https://glif.app/*
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';

    // Modern SVG Icons
    const icons = {
        close: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <line x1="18" y1="6" x2="6" y2="18"/>
            <line x1="6" y1="6" x2="18" y2="18"/>
        </svg>`,
        generate: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <circle cx="12" cy="12" r="10"/>
            <line x1="12" y1="8" x2="12" y2="16"/>
            <line x1="8" y1="12" x2="16" y2="12"/>
        </svg>`,
        loading: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="animate-spin">
            <circle cx="12" cy="12" r="10"/>
            <path d="M12 2a10 10 0 0 1 10 10"/>
        </svg>`
    };

    // Inject styles
    function injectStyles() {
        const styles = `
            .ai-batch-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-batch-panel {
                background: white;
                border-radius: 12px;
                padding: 24px;
                width: 90%;
                max-width: 500px;
                max-height: 90vh;
                overflow-y: auto;
                box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            }

            .ai-batch-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-bottom: 20px;
            }

            .ai-batch-header h2 {
                margin: 0;
                font-size: 1.5rem;
                font-weight: 600;
            }

            .ai-close-button {
                background: none;
                border: none;
                cursor: pointer;
                padding: 4px;
                color: #666;
                transition: color 0.2s;
            }

            .ai-close-button:hover {
                color: #000;
            }

            .ai-input-field {
                margin-bottom: 16px;
            }

            .ai-input-label {
                display: block;
                margin-bottom: 8px;
                font-weight: 500;
                color: #333;
            }

            .ai-input {
                width: 100%;
                padding: 8px 12px;
                border: 1px solid #ddd;
                border-radius: 6px;
                font-size: 14px;
                transition: border-color 0.2s;
            }

            .ai-input:focus {
                outline: none;
                border-color: #0066ff;
            }

            .ai-generate-button {
                width: 100%;
                padding: 12px;
                background: rgb(100 48 247);
                color: white;
                border: none;
                border-radius: 6px;
                font-weight: 500;
                cursor: pointer;
                display: flex;
                align-items: center;
                justify-content: center;
                gap: 8px;
                height: 48px;
                transition: background-color 0.2s;
            }

            .ai-generate-button:hover {
                background: #0052cc;
            }

            .ai-generate-button:disabled {
                background: #ccc;
                cursor: not-allowed;
            }

            .progress-container {
                margin-top: 16px;
            }

            .progress-bar {
                width: 100%;
                height: 4px;
                background: #eee;
                border-radius: 2px;
                overflow: hidden;
            }

            .progress-fill {
                height: 100%;
                background: #0066ff;
                transition: width 0.3s ease;
            }

            .progress-info {
                display: flex;
                justify-content: space-between;
                margin-top: 8px;
                font-size: 14px;
                color: #666;
            }

            .progress-message {
                margin-top: 8px;
                font-size: 14px;
                color: #666;
                text-align: center;
            }

            .review-container {
                margin-top: 20px;
                max-height: 60vh;
                overflow-y: auto;
                padding-right: 8px;
            }

            .review-container::-webkit-scrollbar {
                width: 8px;
            }

            .review-container::-webkit-scrollbar-track {
                background: #f1f1f1;
                border-radius: 4px;
            }

            .review-container::-webkit-scrollbar-thumb {
                background: rgb(100 48 247);
                border-radius: 4px;
            }

            .review-item {
                background: #f8f9fa;
                border-radius: 8px;
                padding: 16px;
                margin-bottom: 12px;
                border: 1px solid #e9ecef;
            }

            .review-item-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-bottom: 12px;
            }

            .review-item-number {
                font-weight: 600;
                color: rgb(100 48 247);
            }

            .review-field {
                margin-bottom: 8px;
            }

            .review-field-label {
                font-size: 12px;
                color: #666;
                margin-bottom: 4px;
            }

            .review-field-input {
                width: 100%;
                padding: 8px;
                border: 1px solid #ddd;
                border-radius: 4px;
                font-size: 14px;
                transition: border-color 0.2s;
            }

            .review-field-input:focus {
                outline: none;
                border-color: rgb(100 48 247);
            }

            .review-actions {
                margin-top: 20px;
                display: flex;
                gap: 12px;
            }

            .review-button {
                flex: 1;
                padding: 12px;
                border: none;
                border-radius: 6px;
                font-weight: 500;
                cursor: pointer;
                height: 48px;
                display: flex;
                align-items: center;
                justify-content: center;
                gap: 8px;
                transition: all 0.2s;
            }

            .review-generate {
                background: rgb(100 48 247);
                color: white;
            }

            .review-generate:hover {
                background: rgb(85 41 210);
            }

            .review-back {
                background: #f8f9fa;
                color: #666;
                border: 1px solid #ddd;
            }

            .review-back:hover {
                background: #e9ecef;
            }

            .results-grid {
                display: grid;
                grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
                gap: 20px;
                padding: 20px;
                margin-top: 20px;
            }

            .result-card {
                background: white;
                border-radius: 12px;
                overflow: hidden;
                box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
                transition: transform 0.2s;
                position: relative;
            }

            .result-card:hover {
                transform: translateY(-2px);
            }

            .result-image-container {
                position: relative;
                padding-top: 100%;
                background: #f8f9fa;
            }

            .result-image {
                position: absolute;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                object-fit: contain;
            }

            .result-loading {
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                color: rgb(100 48 247);
            }

            .result-error {
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                color: #dc3545;
                text-align: center;
                padding: 20px;
            }

            .result-details {
                padding: 16px;
            }

            .result-field {
                margin-bottom: 8px;
            }

            .result-field-label {
                font-size: 12px;
                color: #666;
                margin-bottom: 2px;
            }

            .result-field-value {
                font-size: 14px;
                color: #333;
                word-break: break-word;
            }

            .generation-progress {
                position: fixed;
                bottom: 20px;
                left: 50%;
                transform: translateX(-50%);
                background: white;
                padding: 16px;
                border-radius: 8px;
                box-shadow: 0 4px 6px rgba(0,0,0,0.1);
                display: flex;
                align-items: center;
                gap: 12px;
                z-index: 10000;
            }

            .generation-progress-bar {
                width: 200px;
                height: 4px;
                background: #eee;
                border-radius: 2px;
                overflow: hidden;
            }

            .generation-progress-fill {
                height: 100%;
                background: rgb(100 48 247);
                transition: width 0.3s ease;
            }

            .generation-progress-text {
                font-size: 14px;
                color: #666;
                white-space: nowrap;
            }

            .animate-spin {
                animation: spin 1s linear infinite;
            }

            @keyframes spin {
                from { transform: rotate(0deg); }
                to { transform: rotate(360deg); }
            }

            .batch-results-container {
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                width: 80%;
                max-width: 900px;
                max-height: 80vh;
                background: white;
                border-radius: 12px;
                box-shadow: 0 4px 24px rgba(0,0,0,0.15);
                z-index: 10000;
                overflow: hidden;
                display: flex;
                flex-direction: column;
            }

            .batch-results-header {
                padding: 16px;
                border-bottom: 1px solid #eee;
                display: flex;
                justify-content: space-between;
                align-items: center;
                background: #fafafa;
            }

            .batch-results-header h2 {
                margin: 0;
                font-size: 18px;
            }

            .batch-results-header .close-button {
                background: none;
                border: none;
                padding: 8px;
                cursor: pointer;
                color: #666;
                font-size: 20px;
                line-height: 1;
            }

            .batch-results-content {
                flex: 1;
                overflow-y: auto;
                padding: 16px;
            }

            .batch-results-grid {
                display: grid;
                grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
                gap: 16px;
            }
        `;

        const styleElement = document.createElement('style');
        styleElement.textContent = styles;
        document.head.appendChild(styleElement);
    }

    // Get form inputs
    function getWorkflowInputs() {
        const form = document.querySelector('form');
        if (!form) return [];

        const inputs = [];
        form.querySelectorAll('textarea').forEach(textarea => {
            if (textarea.name && !textarea.name.startsWith('__') && textarea.name !== 'spellId' && textarea.name !== 'version') {
                const label = textarea.closest('label')?.querySelector('span')?.textContent?.trim() || '';
                inputs.push({
                    name: textarea.name,
                    type: 'textarea',
                    label: label,
                    value: textarea.value.trim(),
                    placeholder: textarea.getAttribute('placeholder') || label
                });
            }
        });
        return inputs;
    }

    // Show toast notification
    function showToast(message, type = 'success') {
        const toast = document.createElement('div');
        toast.style.cssText = `
            position: fixed;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            padding: 12px 24px;
            background: ${type === 'error' ? '#ff4444' : type === 'warning' ? '#ffbb33' : '#00C851'};
            color: white;
            border-radius: 6px;
            font-size: 14px;
            z-index: 10000;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
        `;
        toast.textContent = message;
        document.body.appendChild(toast);
        setTimeout(() => toast.remove(), 3000);
    }

    // Fetch AI batch inputs
    async function fetchAIBatchInputs(amount, content) {
        const formInputs = getWorkflowInputs();

        // Get workflow name and description
        const workflowTitle = document.querySelector('h1')?.textContent || '';
        const workflowDescription = document.querySelector('.text-gray-500')?.textContent?.trim() || '';

        // Format input fields with rich context
        const enrichedFields = formInputs.map(input => ({
            name: input.label,
            type: input.type,
            currentValue: input.value,
            placeholder: input.placeholder,
            constraints: input.type === 'number' ? {
                min: input.min,
                max: input.max,
                step: input.step
            } : null
        }));

        // Get previous successful generations if available
        const previousGenerations = Array.from(document.querySelectorAll('.workflow-result'))
            .slice(0, 3)  // Take up to 3 recent examples
            .map(result => {
                const inputs = {};
                result.querySelectorAll('.input-value').forEach(input => {
                    inputs[input.getAttribute('data-name')] = input.textContent.trim();
                });
                return inputs;
            });

        console.log('Sending enriched context:', { workflowTitle, enrichedFields, previousGenerations });

        try {
            const enrichedContext = JSON.stringify({
                workflow: {
                    title: workflowTitle,
                    description: workflowDescription
                },
                fields: enrichedFields,
                examples: previousGenerations
            });

            const payload = {
                id: "cm4b89oo000asm86fstry7u1e",
                version: "live",
                inputs: {
                    amount: amount.toString(),
                    fields: formInputs.map(input => input.name).join(' | '),
                    content: content,
                    enrichedContext: enrichedContext
                },
                glifRunIsPublic: !GM_getValue('isPrivate', false)
            };

            console.log('Debug - Request payload:', JSON.stringify(payload, null, 2));

            const response = await fetch("https://glif.app/api/run-glif", {
                method: 'POST',
                credentials: 'include',
                headers: {
                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0',
                    'Accept': '*/*',
                    'Accept-Language': 'en-US,en;q=0.5',
                    'Content-Type': 'application/json',
                    'Sec-GPC': '1',
                    'Sec-Fetch-Dest': 'empty',
                    'Sec-Fetch-Mode': 'cors',
                    'Sec-Fetch-Site': 'same-origin',
                    'Priority': 'u=4'
                },
                referrer: `https://glif.app/@appelsiensam/glifs/${window.location.pathname.split('/').pop()}`,
                mode: 'cors',
                body: JSON.stringify(payload)
            });

            if (!response.ok) {
                const errorText = await response.text();
                console.log('Debug - Error response:', errorText);
                throw new Error(`API request failed: ${response.status}\nResponse: ${errorText}`);
            }

            const reader = response.body.getReader();
            let jsonData = '';
            let entries = [];

            while (true) {
                const { done, value } = await reader.read();
                if (done) break;

                const chunk = new TextDecoder().decode(value);
                jsonData += chunk;

                const lines = jsonData.split('\n');
                jsonData = lines.pop() || '';

                for (const line of lines) {
                    if (!line.trim().startsWith('data: ')) continue;

                    try {
                        const data = JSON.parse(line.slice(6));
                        const text = data.graphExecutionState?.nodes?.text1?.output?.value;

                        if (text?.includes('"entries"')) {
                            try {
                                const parsed = JSON.parse(text);
                                if (parsed.entries?.length) entries = parsed.entries;
                            } catch (e) {
                                console.log('Partial JSON:', text);
                            }
                        }
                    } catch (e) {
                        console.log('Parse error:', e);
                    }
                }
            }

            return entries;
        } catch (error) {
            console.error('fetchAIBatchInputs error:', error);
            throw error;
        }
    }

    // Process batch generation with parallel processing
    async function processBatchGeneration(entries) {
        const spellId = window.location.pathname.split('/').pop();
        const isPrivate = GM_getValue('isPrivate', false);

        console.log('Starting generation with spell ID:', spellId);
        console.log('Entries to process:', entries);

        // Create results container if it doesn't exist
        let resultsContainer = document.querySelector('.batch-results-container');
        if (!resultsContainer) {
            resultsContainer = document.createElement('div');
            resultsContainer.className = 'batch-results-container';
            resultsContainer.style.cssText = `
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                width: 80%;
                max-width: 900px;
                max-height: 80vh;
                background: white;
                border-radius: 12px;
                box-shadow: 0 4px 24px rgba(0,0,0,0.15);
                z-index: 10000;
                overflow: hidden;
                display: flex;
                flex-direction: column;
            `;

            // Add header with title and close button
            const header = document.createElement('div');
            header.style.cssText = `
                padding: 16px;
                border-bottom: 1px solid #eee;
                display: flex;
                justify-content: space-between;
                align-items: center;
                background: #fafafa;
            `;
            header.innerHTML = `
                <h2 style="margin: 0; font-size: 18px;">Generated Images</h2>
                <button class="close-button" style="
                    background: none;
                    border: none;
                    padding: 8px;
                    cursor: pointer;
                    color: #666;
                    font-size: 20px;
                    line-height: 1;
                ">×</button>
            `;
            resultsContainer.appendChild(header);

            // Add close button functionality
            header.querySelector('.close-button').addEventListener('click', () => {
                resultsContainer.remove();
            });

            // Add overlay
            const overlay = document.createElement('div');
            overlay.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background: rgba(0,0,0,0.5);
                z-index: 9999;
            `;
            document.body.appendChild(overlay);

            // Close on overlay click
            overlay.addEventListener('click', () => {
                overlay.remove();
                resultsContainer.remove();
            });

            document.body.appendChild(resultsContainer);
        }

        // Create scrollable content area
        const contentArea = document.createElement('div');
        contentArea.style.cssText = `
            flex: 1;
            overflow-y: auto;
            padding: 16px;
        `;
        resultsContainer.appendChild(contentArea);

        // Create results grid with improved layout
        const resultsGrid = document.createElement('div');
        resultsGrid.className = 'batch-results-grid';
        resultsGrid.style.cssText = `
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
            gap: 16px;
        `;
        contentArea.appendChild(resultsGrid);

        // Create progress indicator
        const progress = document.createElement('div');
        progress.className = 'generation-progress';
        progress.style.cssText = `
            position: fixed;
            bottom: 0;
            left: 0;
            right: 0;
            background: white;
            padding: 8px 16px;
            box-shadow: 0 -2px 8px rgba(0,0,0,0.1);
            z-index: 10001;
        `;
        progress.innerHTML = `
            <div class="generation-progress-bar" style="
                height: 4px;
                background: #f0f0f0;
                border-radius: 2px;
                overflow: hidden;
            ">
                <div class="generation-progress-fill" style="
                    width: 0%;
                    height: 100%;
                    background: rgb(100, 48, 247);
                    transition: width 0.3s ease;
                "></div>
            </div>
            <div class="generation-progress-text" style="
                text-align: center;
                margin-top: 4px;
                font-size: 14px;
            ">Generating 0/${entries.length}</div>
        `;
        document.body.appendChild(progress);

        let completed = 0;
        const updateProgress = () => {
            completed++;
            const percentage = (completed / entries.length) * 100;
            progress.querySelector('.generation-progress-fill').style.width = `${percentage}%`;
            progress.querySelector('.generation-progress-text').textContent =
                `Generating ${completed}/${entries.length}`;
        };

        // Create result cards for each entry
        const resultCards = entries.map((entry, index) => {
            const card = document.createElement('div');
            card.className = 'result-card';
            card.style.cssText = `
                background: white;
                border-radius: 8px;
                box-shadow: 0 2px 8px rgba(0,0,0,0.1);
                overflow: hidden;
            `;
            card.innerHTML = `
                <div class="result-image-container" style="
                    aspect-ratio: ${entry.widthinput}/${entry.heightinput};
                    background: #f5f5f5;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                ">
                    <div class="result-loading">${icons.loading}</div>
                </div>
                <div class="result-details" style="padding: 12px;">
                    ${Object.entries(entry).map(([key, value]) => `
                        <div class="result-field" style="margin-bottom: 8px;">
                            <div class="result-field-label" style="
                                font-size: 12px;
                                color: #666;
                                margin-bottom: 2px;
                            ">${key.charAt(0).toUpperCase() + key.slice(1)}</div>
                            <div class="result-field-value" style="
                                font-size: 14px;
                                word-break: break-word;
                            ">${value}</div>
                        </div>
                    `).join('')}
                </div>
            `;
            resultsGrid.appendChild(card);
            return card;
        });

        // Process all entries in parallel
        const results = await Promise.all(entries.map(async (entry, index) => {
            try {
                console.log(`Generating image ${index + 1} with inputs:`, entry);

                const requestBody = {
                    id: spellId,
                    version: "live",
                    inputs: {
                        ...entry,
                        heightinput: String(entry.heightinput),
                        widthinput: String(entry.widthinput)
                    },
                    glifRunIsPublic: !isPrivate
                };

                console.log('Request body:', requestBody);

                const response = await fetch("https://glif.app/api/run-glif", {
                    method: 'POST',
                    credentials: 'include',
                    headers: {
                        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0',
                        'Accept': '*/*',
                        'Accept-Language': 'en-US,en;q=0.5',
                        'Content-Type': 'application/json',
                        'Sec-GPC': '1',
                        'Sec-Fetch-Dest': 'empty',
                        'Sec-Fetch-Mode': 'cors',
                        'Sec-Fetch-Site': 'same-origin',
                        'Priority': 'u=4'
                    },
                    referrer: `https://glif.app/@appelsiensam/glifs/${spellId}`,
                    mode: 'cors',
                    body: JSON.stringify(requestBody)
                });

                if (!response.ok) {
                    const errorText = await response.text();
                    console.error('API error:', errorText);
                    throw new Error(`Generation failed: ${response.status} - ${errorText}`);
                }

                const reader = response.body.getReader();
                let imageUrl = null;
                let jsonData = '';

                while (true) {
                    const { done, value } = await reader.read();
                    if (done) break;

                    const chunk = new TextDecoder().decode(value);
                    console.log(`Chunk received for image ${index + 1}:`, chunk);

                    const lines = chunk.split('\n');
                    for (const line of lines) {
                        if (!line.trim().startsWith('data: ')) continue;

                        try {
                            const data = JSON.parse(line.slice(6));

                            // Check for image URL in various locations
                            const imageValue =
                                data.graphExecutionState?.finalOutput?.value ||
                                data.graphExecutionState?.nodes?.output1?.output?.value ||
                                data.graphExecutionState?.nodes?.image?.output?.value;

                            if (imageValue) {
                                imageUrl = imageValue;
                                console.log(`Found image URL for ${index + 1}:`, imageUrl);

                                // Update card with image immediately
                                const card = resultCards[index];
                                const container = card.querySelector('.result-image-container');
                                container.innerHTML = `<img src="${imageUrl}" class="result-image" style="
                                    max-width: 100%;
                                    height: auto;
                                    display: block;
                                " alt="Generated image ${index + 1}">`;

                                break;
                            }

                            // Check if generation is complete
                            if (data.graphExecutionState?.status === 'done') {
                                console.log(`Generation complete for ${index + 1}`);
                                break;
                            }
                        } catch (e) {
                            console.log('Parse error:', e);
                        }
                    }

                    if (imageUrl) break;
                }

                updateProgress();
                return { success: true, imageUrl, entry };

            } catch (error) {
                console.error(`Error generating image ${index + 1}:`, error);

                // Update card with error
                const card = resultCards[index];
                const container = card.querySelector('.result-image-container');
                container.innerHTML = `
                    <div class="result-error" style="
                        padding: 16px;
                        color: #e53935;
                        text-align: center;
                    ">
                        <div>Generation failed</div>
                        <div style="font-size: 12px; margin-top: 4px;">${error.message}</div>
                    </div>
                `;

                updateProgress();
                return { success: false, error: error.message, entry };
            }
        }));

        // Keep progress indicator for a moment before removing
        setTimeout(() => progress.remove(), 2000);

        const successfulResults = results.filter(r => r.success);
        console.log('Generation complete. Successful results:', successfulResults);

        return results;
    }

    // Display AI batch panel
    function displayAIBatchPanel() {
        const overlay = document.createElement('div');
        overlay.className = 'ai-batch-overlay';

        const panel = document.createElement('div');
        panel.className = 'ai-batch-panel';

        const header = document.createElement('div');
        header.className = 'ai-batch-header';

        const title = document.createElement('h2');
        title.textContent = 'Batch Generator';

        const closeButton = document.createElement('button');
        closeButton.className = 'ai-close-button';
        closeButton.innerHTML = icons.close;
        closeButton.addEventListener('click', () => {
            if (!panel.dataset.generating) {
                overlay.remove();
            } else {
                showToast('Please wait for generation to complete', 'warning');
            }
        });

        header.appendChild(title);
        header.appendChild(closeButton);

        const content = document.createElement('div');
        content.className = 'ai-batch-content';

        const amountField = document.createElement('div');
        amountField.className = 'ai-input-field';

        const amountLabel = document.createElement('label');
        amountLabel.className = 'ai-input-label';
        amountLabel.textContent = 'Number of Images';

        const amountInput = document.createElement('input');
        amountInput.type = 'number';
        amountInput.className = 'ai-input';
        amountInput.min = '1';
        amountInput.max = '100';
        amountInput.value = '1';

        amountField.appendChild(amountLabel);
        amountField.appendChild(amountInput);

        const contentField = document.createElement('div');
        contentField.className = 'ai-input-field';

        const contentLabel = document.createElement('label');
        contentLabel.className = 'ai-input-label';
        contentLabel.textContent = 'Description';

        const contentInput = document.createElement('textarea');
        contentInput.className = 'ai-input';
        contentInput.rows = 4;
        contentInput.placeholder = 'Describe what you want to generate...';

        contentField.appendChild(contentLabel);
        contentField.appendChild(contentInput);

        const generateButton = document.createElement('button');
        generateButton.className = 'ai-generate-button';
        generateButton.style.cssText = `
            width: 100%;
            padding: 12px;
            background: rgb(100 48 247);
            color: white;
            border: none;
            border-radius: 6px;
            font-weight: 500;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
            height: 48px;
            transition: background-color 0.2s;
        `;
        generateButton.innerHTML = `${icons.generate}<span>Generate Images</span>`;

        const progressContainer = document.createElement('div');
        progressContainer.className = 'progress-container';
        progressContainer.style.display = 'none';
        progressContainer.innerHTML = `
            <div class="progress-bar">
                <div class="progress-fill" style="width: 0%"></div>
            </div>
            <div class="progress-info">
                <span class="progress-count">0/${amountInput.value}</span>
                <span class="progress-percentage">0%</span>
            </div>
            <div class="progress-message">Starting generation...</div>
        `;

        generateButton.addEventListener('click', async () => {
            const amount = parseInt(amountInput.value);
            const content = contentInput.value;

            if (!content) {
                showToast('Please describe the content you want to generate', 'error');
                return;
            }

            if (isNaN(amount) || amount < 1 || amount > 100) {
                showToast('Please enter a valid number of images (1-100)', 'error');
                return;
            }

            generateButton.disabled = true;
            generateButton.innerHTML = `${icons.loading}<span>Generating Prompts...</span>`;

            try {
                console.log('Fetching AI batch inputs...');
                const inputs = await fetchAIBatchInputs(amount, content);
                console.log('Received inputs:', inputs);

                if (inputs && inputs.length > 0) {
                    // Show review screen instead of generating immediately
                    displayReviewScreen(inputs, panel, {
                        amount,
                        content
                    });
                } else {
                    showToast('Failed to generate image prompts', 'error');
                    generateButton.disabled = false;
                    generateButton.innerHTML = `${icons.generate}<span>Generate Images</span>`;
                }
            } catch (error) {
                console.error('Batch generation error:', error);
                showToast('Failed to generate prompts: ' + error.message, 'error');
                generateButton.disabled = false;
                generateButton.innerHTML = `${icons.generate}<span>Generate Images</span>`;
            }
        });

        content.appendChild(amountField);
        content.appendChild(contentField);
        content.appendChild(generateButton);
        content.appendChild(progressContainer);

        panel.appendChild(header);
        panel.appendChild(content);
        overlay.appendChild(panel);
        document.body.appendChild(overlay);
    }

    // Display review screen
    function displayReviewScreen(entries, panel, originalInputs) {
        // Clear existing content
        const content = panel.querySelector('.ai-batch-content');
        content.innerHTML = '';

        // Create review container
        const reviewContainer = document.createElement('div');
        reviewContainer.className = 'review-container';

        // Add each entry for review
        entries.forEach((entry, index) => {
            const reviewItem = document.createElement('div');
            reviewItem.className = 'review-item';

            const header = document.createElement('div');
            header.className = 'review-item-header';
            header.innerHTML = `<span class="review-item-number">Image ${index + 1}</span>`;

            reviewItem.appendChild(header);

            // Add editable fields
            Object.entries(entry).forEach(([key, value]) => {
                const field = document.createElement('div');
                field.className = 'review-field';

                const label = document.createElement('div');
                label.className = 'review-field-label';
                label.textContent = key.charAt(0).toUpperCase() + key.slice(1);

                const input = document.createElement('input');
                input.className = 'review-field-input';
                input.type = 'text';
                input.value = value;
                input.dataset.index = index;
                input.dataset.field = key;

                // Update entries object when input changes
                input.addEventListener('input', (e) => {
                    entries[index][key] = e.target.value;
                });

                field.appendChild(label);
                field.appendChild(input);
                reviewItem.appendChild(field);
            });

            reviewContainer.appendChild(reviewItem);
        });

        // Add action buttons
        const actions = document.createElement('div');
        actions.className = 'review-actions';

        const backButton = document.createElement('button');
        backButton.className = 'review-button review-back';
        backButton.innerHTML = `<span>Back</span>`;
        backButton.addEventListener('click', () => {
            // Restore original panel content
            displayAIBatchPanel();
        });

        const generateButton = document.createElement('button');
        generateButton.className = 'review-button review-generate';
        generateButton.innerHTML = `${icons.generate}<span>Generate Images</span>`;
        generateButton.addEventListener('click', async () => {
            generateButton.disabled = true;
            generateButton.innerHTML = `${icons.loading}<span>Generating...</span>`;

            try {
                const results = await processBatchGeneration(entries);
                console.log('Generation complete:', results);

                const successCount = results.filter(r => r.success).length;
                showToast(`Successfully generated ${successCount} images`);

                if (successCount === 0) {
                    generateButton.disabled = false;
                    generateButton.innerHTML = `${icons.generate}<span>Generate Images</span>`;
                } else {
                    panel.closest('.ai-batch-overlay').remove();
                }
            } catch (error) {
                console.error('Generation error:', error);
                showToast('Failed to generate images: ' + error.message, 'error');
                generateButton.disabled = false;
                generateButton.innerHTML = `${icons.generate}<span>Generate Images</span>`;
            }
        });

        actions.appendChild(backButton);
        actions.appendChild(generateButton);

        content.appendChild(reviewContainer);
        content.appendChild(actions);
    }

    // Add the AI batch button to the page
    function addAIBatchButton() {
        const container = document.querySelector('form');
        if (!container) return;

        const button = document.createElement('button');
        button.className = 'ai-batch-button';
        button.style.cssText = `
            margin-top: 12px;
            padding: 8px 16px;
            background: rgb(100 48 247);
            color: white;
            border: none;
            border-radius: 6px;
            font-weight: 500;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
            width: 100%;
            height: 48px;
            transition: background-color 0.2s;
        `;
        button.innerHTML = `${icons.generate}<span>Batch Generator</span>`;
        button.addEventListener('click', (e) => {
            e.preventDefault();
            displayAIBatchPanel();
        });

        container.appendChild(button);
    }

    // Initialize
    function initialize() {
        injectStyles();

        // Wait for the form to be ready
        const observer = new MutationObserver((mutations, obs) => {
            if (document.querySelector('form')) {
                addAIBatchButton();
                obs.disconnect();
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    // Start the script
    initialize();
})();