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