GLIF AI Batch Generator

AI-powered batch image generation for GLIF

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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();
})();