您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A set of tools i use in glif.app
// ==UserScript== // @name GLIF Tools V2 // @namespace http://tampermonkey.net/ // @version 2.0 // @description A set of tools i use in glif.app // @author i12bp8 // @match https://glif.app/* // @run-at document-start // @grant GM_setValue // @grant GM_getValue // @grant unsafeWindow // ==/UserScript== (function() { 'use strict'; // Modern SVG Icons const icons = { history: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M12 8v4l3 3"></path> <path d="M3.05 11a9 9 0 1 1 .5 4"></path> </svg>`, tools: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-code-slash" viewBox="0 0 16 16"> <path d="M10.478 1.647a.5.5 0 1 0-.956-.294l-4 13a.5.5 0 0 0 .956.294zM4.854 4.146a.5.5 0 0 1 0 .708L1.707 8l3.147 3.146a.5.5 0 0 1-.708.708l-3.5-3.5a.5.5 0 0 1 0-.708l3.5-3.5a.5.5 0 0 1 .708 0m6.292 0a.5.5 0 0 0 0 .708L14.293 8l-3.147 3.146a.5.5 0 0 0 .708.708l3.5-3.5a.5.5 0 0 0 0-.708l-3.5-3.5a.5.5 0 0 1 .708 0"></path> </svg>`, trash: `<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M2.5 5h15M6.67 5V3.33a1.67 1.67 0 011.66-1.66h3.34a1.67 1.67 0 011.66 1.66V5m2.5 0v11.67a1.67 1.67 0 01-1.66 1.66H5.83a1.67 1.67 0 01-1.66-1.66V5h11.66z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M8.33 9.17v5M11.67 9.17v5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </svg>`, private: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path> </svg>`, public: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"></path> </svg>`, time: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="10"></circle> <polyline points="12 6 12 12 16 14"></polyline> </svg>`, search: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="11" cy="11" r="8"></circle> <line x1="21" y1="21" x2="16.65" y2="16.65"></line> </svg>`, prompt: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path> </svg>`, batch: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <rect x="3" y="3" width="18" height="18" rx="2" /> <line x1="8" y1="12" x2="16" y2="12" /> <line x1="8" y1="8" x2="16" y2="8" /> <line x1="8" y1="16" x2="16" y2="16" /> </svg>`, add: `<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>`, remove: `<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="8" y1="12" x2="16" y2="12"/> </svg>`, play: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <circle cx="12" cy="12" r="10"/> <polygon points="10 8 16 12 10 16" fill="currentColor"/> </svg>`, lock: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <rect x="3" y="11" width="18" height="11" rx="2" ry="2"/> <path d="M7 11V7a5 5 0 0 1 10 0v4"/> </svg>`, 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>`, error: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="10"></circle> <line x1="15" y1="9" x2="9" y2="15"></line> <line x1="9" y1="9" x2="15" y2="15"></line> </svg>`, loading: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path> <path d="M12 12l-.01 0"></path> </svg>`, check: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <polyline points="20 6 9 17 4 12"></polyline> </svg>`, globe: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="10"></circle> <line x1="2" y1="12" x2="22" y2="12"></line> <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path> </svg>`, lock: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <rect x="3" y="11" width="18" height="11" rx="2" fill="none"></rect> <path d="M7 11V7a5 5 0 0 1 10 0v4"></path> </svg>`, image: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <rect x="3" y="3" width="18" height="18" rx="2" fill="none"></rect> <circle cx="8.5" cy="8.5" r="1.5"></circle> <path d="M21 21.35l-9-9"></path> </svg>`, copy: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/> <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/> </svg>`, generate: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/> <path d="M9 12l2 2 4-4"/> </svg>`, bug: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="currentColor" class="bi bi-bug" style="min-width: 20px;"> <path d="M4.355.522a.5.5 0 0 1 .623.333l.291.956A5 5 0 0 1 8 1c1.007 0 1.946.298 2.731.811l.29-.956a.5.5 0 1 1 .957.29l-.41 1.352A5 5 0 0 1 13 6h.5a.5.5 0 0 0 .5-.5V5a.5.5 0 0 1 1 0v.5A1.5 1.5 0 0 1 13.5 7H13v1h1.5a.5.5 0 0 1 0 1H13v1h.5a1.5 1.5 0 0 1 1.5 1.5v.5a.5.5 0 0 1-1 0v-.5a.5.5 0 0 0-.5-.5H13a5 5 0 0 1-10 0h-.5a.5.5 0 0 0-.5.5v.5a.5.5 0 1 1-1 0v-.5A1.5 1.5 0 0 1 2.5 10H3V9H1.5a.5.5 0 0 0 0-1H3V7h-.5A1.5 1.5 0 0 1 1 5.5V5a.5.5 0 0 1 1 0v.5a.5.5 0 0 0 .5.5H3c0-1.364.547-2.601 1.432-3.503l-.41-1.352a.5.5 0 0 1 .333-.623M4 7v4a4 4 0 0 0 3.5 3.97V7zm4.5 0v7.97A4 4 0 0 0 12 11V7zM12 6a4 4 0 0 0-1.334-2.982A3.98 3.98 0 0 0 8 2a3.98 3.98 0 0 0-2.667 1.018A4 4 0 0 0 4 6z"/> </svg>`, feature: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star" viewBox="0 0 16 16"> <path d="M2.866 14.85c-.078.444.36.791.746.593l4.39-2.256 4.389 2.256c.386.198.824-.149.746-.592l-.83-4.73 3.522-3.356c.33-.314.16-.888-.282-.95l-4.898-.696L8.465.792a.513.513 0 0 0-.927 0L5.354 5.12l-4.898.696c-.441.062-.612.636-.283.95l3.523 3.356-.83 4.73zm4.905-2.767-3.686 1.894.694-3.957a.56.56 0 0 0-.163-.505L1.71 6.745l4.052-.576a.53.53 0 0 0 .393-.288L8 2.223l1.847 3.658a.53.53 0 0 0 .393.288l4.052.575-2.906 2.77a.56.56 0 0 0-.163.506l.694 3.957-3.686-1.894a.5.5 0 0 0-.461 0z"/> </svg>`, moon: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <path d="M6 .278a.77.77 0 0 1 .08.858 7.2 7.2 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277q.792-.001 1.533-.16a.79.79 0 0 1 .81.316.73.73 0 0 1-.031.893A8.35 8.35 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.75.75 0 0 1 6 .278"/> </svg>`, sun: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8M8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0m0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13m8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5M3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8m10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0m-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0m9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707M4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707"/> </svg>` }; // Initialize dark mode from localStorage let isDarkMode = localStorage.getItem('glifToolsDarkMode') === 'true'; // Toggle dark mode const toggleDarkMode = () => { isDarkMode = !isDarkMode; localStorage.setItem('glifToolsDarkMode', isDarkMode); document.documentElement.setAttribute('data-glif-dark-mode', isDarkMode); // Force update all dropdowns to ensure correct state const dropdowns = document.querySelectorAll('.glif-tools-dropdown'); dropdowns.forEach(dropdown => { const oldMenu = dropdown.querySelector('.glif-tools-menu'); if (oldMenu) { const newDropdown = createToolsDropdown(); dropdown.parentNode.replaceChild(newDropdown, dropdown); } }); // Update all panels and their contents const updatePanelStyles = (panel) => { if (isDarkMode) { panel.style.backgroundColor = '#1E1E1E'; panel.style.color = '#FFFFFF'; panel.style.borderColor = '#2E2E2E'; panel.style.boxShadow = '0 8px 24px rgba(0, 0, 0, 0.35)'; // Update header const header = panel.querySelector('.panel-header, .history-header, .batch-header, .metadata-header, .bug-report-header'); if (header) { header.style.backgroundColor = '#1E1E1E'; header.style.borderBottomColor = '#2E2E2E'; const title = header.querySelector('h2'); if (title) title.style.color = '#FFFFFF'; } // Update content const content = panel.querySelector('.panel-content, .history-content, .batch-content, .metadata-content, .bug-report-content'); if (content) { content.style.backgroundColor = '#1E1E1E'; } // Update form elements panel.querySelectorAll('input, textarea, select').forEach(el => { el.style.backgroundColor = '#2A2A2A'; el.style.color = '#FFFFFF'; el.style.borderColor = '#3E3E3E'; }); // Update buttons panel.querySelectorAll('button').forEach(btn => { btn.style.backgroundColor = '#2A2A2A'; btn.style.color = '#FFFFFF'; btn.style.borderColor = '#3E3E3E'; }); // Update history/batch items panel.querySelectorAll('.history-item, .batch-result-item').forEach(item => { item.style.backgroundColor = '#2A2A2A'; item.style.borderColor = '#3E3E3E'; }); // Update metadata panel.querySelectorAll('.history-metadata, .batch-metadata').forEach(meta => { meta.style.color = '#FFFFFF'; }); // Update timestamps and status panel.querySelectorAll('.history-timestamp, .history-status').forEach(el => { el.style.color = '#6B7280'; }); // Update progress bars panel.querySelectorAll('.batch-progress-container').forEach(container => { container.style.backgroundColor = '#2A2A2A'; container.style.borderColor = '#3E3E3E'; const bar = container.querySelector('.progress-bar'); if (bar) bar.style.backgroundColor = '#33363C'; const fill = container.querySelector('.progress-fill'); if (fill) fill.style.backgroundColor = '#4F46E5'; }); // Update error containers panel.querySelectorAll('.error-container').forEach(container => { container.style.backgroundColor = 'rgba(239, 68, 68, 0.1)'; container.style.borderColor = 'rgba(239, 68, 68, 0.2)'; const message = container.querySelector('.error-message'); if (message) message.style.color = '#EF4444'; }); } else { // Reset all styles to default panel.style = ''; panel.querySelectorAll('*').forEach(el => { el.style = ''; }); } }; // Update all panels document.querySelectorAll('.glif-panel, .history-panel, .batch-panel, .bug-report-panel, .metadata-panel').forEach(updatePanelStyles); // Update overlays document.querySelectorAll('.history-overlay, .batch-overlay, .metadata-overlay').forEach(overlay => { overlay.style.backgroundColor = isDarkMode ? 'rgba(0, 0, 0, 0.75)' : ''; }); // Update toasts document.querySelectorAll('.glif-toast').forEach(toast => { if (isDarkMode) { toast.style.backgroundColor = '#1E1E1E'; toast.style.color = '#FFFFFF'; toast.style.borderColor = '#2E2E2E'; if (toast.classList.contains('success')) { toast.style.borderLeftColor = '#10B981'; } else if (toast.classList.contains('error')) { toast.style.borderLeftColor = '#EF4444'; } } else { toast.style = ''; } }); }; // Inject modern styles const injectStyles = () => { const styleSheet = document.createElement('style'); styleSheet.id = 'glif-tools-v3-styles'; styleSheet.textContent = ` /* Dark mode transitions */ .glif-panel, .glif-panel *, .glif-tools-menu, .glif-tools-menu *, .glif-toast, input, textarea, button, select { transition: all 0.3s ease; } /* Dark mode styles - Tools Dropdown */ [data-glif-dark-mode="true"] .glif-tools-menu { background-color: #1E1E1E !important; border: 1px solid #2E2E2E !important; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35) !important; } [data-glif-dark-mode="true"] .glif-tools-menu-item { color: #FFFFFF !important; } [data-glif-dark-mode="true"] .glif-tools-menu-item:hover { background-color: #2A2A2A !important; color: #FFFFFF !important; } [data-glif-dark-mode="true"] .glif-tools-menu-item svg { color: #FFFFFF !important; } [data-glif-dark-mode="true"] .glif-tools-menu-item:hover svg { color: #FFFFFF !important; } [data-glif-dark-mode="true"] .glif-tools-credits { border-top: 1px solid #2E2E2E !important; color: #FFFFFF !important; margin-top: 8px !important; padding-top: 8px !important; } [data-glif-dark-mode="true"] .glif-tools-credits a { color: #4F46E5 !important; } [data-glif-dark-mode="true"] .glif-tools-credits a:hover { color: #6366F1 !important; } /* Dark mode styles - Panels */ [data-glif-dark-mode="true"] .glif-panel, [data-glif-dark-mode="true"] .history-panel, [data-glif-dark-mode="true"] .batch-panel, [data-glif-dark-mode="true"] .bug-report-panel, [data-glif-dark-mode="true"] .metadata-panel { background-color: #1E1E1E !important; color: #FFFFFF !important; border: 1px solid #2E2E2E !important; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35) !important; } /* Panel Headers */ [data-glif-dark-mode="true"] .panel-header, [data-glif-dark-mode="true"] .history-header, [data-glif-dark-mode="true"] .batch-header, [data-glif-dark-mode="true"] .metadata-header, [data-glif-dark-mode="true"] .bug-report-header { background-color: #1E1E1E !important; border-bottom: 1px solid #2E2E2E !important; } [data-glif-dark-mode="true"] .panel-header h2, [data-glif-dark-mode="true"] .history-header h2, [data-glif-dark-mode="true"] .batch-header h2, [data-glif-dark-mode="true"] .metadata-header h2, [data-glif-dark-mode="true"] .bug-report-header h2 { color: #FFFFFF !important; } /* Panel Content */ [data-glif-dark-mode="true"] .panel-content, [data-glif-dark-mode="true"] .history-content, [data-glif-dark-mode="true"] .batch-content, [data-glif-dark-mode="true"] .metadata-content, [data-glif-dark-mode="true"] .bug-report-content { background-color: #1E1E1E !important; } /* Form Elements */ [data-glif-dark-mode="true"] .glif-panel input, [data-glif-dark-mode="true"] .glif-panel textarea, [data-glif-dark-mode="true"] .glif-panel select, [data-glif-dark-mode="true"] .search-input, [data-glif-dark-mode="true"] .batch-input { background-color: #2A2A2A !important; color: #FFFFFF !important; border: 1px solid #3E3E3E !important; } [data-glif-dark-mode="true"] .glif-panel input:focus, [data-glif-dark-mode="true"] .glif-panel textarea:focus, [data-glif-dark-mode="true"] .glif-panel select:focus, [data-glif-dark-mode="true"] .search-input:focus, [data-glif-dark-mode="true"] .batch-input:focus { border-color: #4F46E5 !important; box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.2) !important; } [data-glif-dark-mode="true"] .glif-panel input:hover, [data-glif-dark-mode="true"] .glif-panel textarea:hover, [data-glif-dark-mode="true"] .glif-panel select:hover, [data-glif-dark-mode="true"] .search-input:hover, [data-glif-dark-mode="true"] .batch-input:hover { background-color: #33363C !important; } /* Buttons */ [data-glif-dark-mode="true"] .glif-panel button, [data-glif-dark-mode="true"] .batch-generate-button, [data-glif-dark-mode="true"] .clear-history-button, [data-glif-dark-mode="true"] .filter-button, [data-glif-dark-mode="true"] .batch-row-action-button, [data-glif-dark-mode="true"] .glif-type-button { background-color: #2A2A2A !important; color: #FFFFFF !important; border: 1px solid #3E3E3E !important; } [data-glif-dark-mode="true"] .glif-panel button:hover, [data-glif-dark-mode="true"] .batch-generate-button:hover, [data-glif-dark-mode="true"] .clear-history-button:hover, [data-glif-dark-mode="true"] .filter-button:hover, [data-glif-dark-mode="true"] .batch-row-action-button:hover, [data-glif-dark-mode="true"] .glif-type-button:hover { background-color: #33363C !important; border-color: #4F46E5 !important; } /* History Items */ [data-glif-dark-mode="true"] .history-item, [data-glif-dark-mode="true"] .batch-result-item { background-color: #2A2A2A !important; border: 1px solid #3E3E3E !important; } [data-glif-dark-mode="true"] .history-item:hover, [data-glif-dark-mode="true"] .batch-result-item:hover { background-color: #33363C !important; border-color: #4F46E5 !important; } [data-glif-dark-mode="true"] .history-metadata, [data-glif-dark-mode="true"] .batch-metadata { color: #FFFFFF !important; } [data-glif-dark-mode="true"] .history-timestamp, [data-glif-dark-mode="true"] .history-status { color: #6B7280 !important; } [data-glif-dark-mode="true"] .history-status.private { color: #EF4444 !important; } [data-glif-dark-mode="true"] .history-status.public { color: #38A169 !important; } /* Progress Bars */ [data-glif-dark-mode="true"] .batch-progress-container { background-color: #2A2A2A !important; border: 1px solid #3E3E3E !important; } [data-glif-dark-mode="true"] .progress-bar { background-color: #33363C !important; } [data-glif-dark-mode="true"] .progress-fill { background-color: #4F46E5 !important; } /* Error States */ [data-glif-dark-mode="true"] .error-container { background-color: rgba(239, 68, 68, 0.1) !important; border: 1px solid rgba(239, 68, 68, 0.2) !important; } [data-glif-dark-mode="true"] .error-message { color: #EF4444 !important; } /* Overlays */ [data-glif-dark-mode="true"] .history-overlay, [data-glif-dark-mode="true"] .batch-overlay, [data-glif-dark-mode="true"] .metadata-overlay { background-color: rgba(0, 0, 0, 0.75) !important; } /* Toast Notifications */ [data-glif-dark-mode="true"] .glif-toast { background-color: #1E1E1E !important; color: #FFFFFF !important; border: 1px solid #2E2E2E !important; } [data-glif-dark-mode="true"] .glif-toast.success { border-left: 4px solid #10B981 !important; } [data-glif-dark-mode="true"] .glif-toast.error { border-left: 4px solid #EF4444 !important; } /* Regular styles */ .glif-tools-dropdown { position: relative; display: inline-block; } .glif-tools-menu { position: absolute; top: 100%; right: 0; background: white; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); padding: 8px; display: none; z-index: 1000; min-width: 200px; opacity: 0; visibility: hidden; transform: translateY(-10px); transition: all 0.2s ease; } .glif-tools-menu.active { display: block; opacity: 1; visibility: visible; transform: translateY(0); } .glif-tools-menu-item { display: flex; align-items: center; gap: 8px; padding: 8px 12px; color: #374151; text-decoration: none; border-radius: 6px; cursor: pointer; font-size: 14px; width: 100%; white-space: nowrap; } .glif-tools-menu-item svg { width: 18px; height: 18px; flex-shrink: 0; } .glif-tools-menu-item:hover { background: #f3f4f6; } .glif-tools-credits { margin-top: 8px; padding-top: 8px; border-top: 1px solid #e5e7eb; font-size: 12px; color: #6b7280; text-align: center; } .glif-tools-credits a { color: #3b82f6; text-decoration: none; } .glif-tools-credits a:hover { text-decoration: underline; } .history-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(17, 17, 17, 0.7); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 10000; } .history-panel { background: #ffffff; border-radius: 24px; width: 90%; max-width: 1200px; max-height: 90vh; overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2); } .history-header { padding: 24px 32px; background: #ffffff; display: flex; flex-direction: column; gap: 24px; border-bottom: 1px solid #edf2f7; } .history-panel-title-section { display: flex; align-items: center; justify-content: space-between; } .history-panel-title { display: flex; align-items: center; gap: 12px; margin: 0; font-size: 1.5rem; font-weight: 700; color: #2d3748; } .history-controls { display: flex; align-items: center; justify-content: space-between; gap: 20px; } .history-filters { display: flex; align-items: center; gap: 8px; background: #f7fafc; padding: 4px; border-radius: 12px; } .filter-button { padding: 8px 16px; border-radius: 8px; border: none; background: transparent; color: #718096; cursor: pointer; transition: all 0.2s ease; font-size: 0.9rem; font-weight: 500; } .filter-button:hover { color: #4a5568; } .filter-button.active { background: #ffffff; color: #2d3748; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); } .search-container { position: relative; flex: 1; max-width: 300px; } .search-input { width: 100%; padding: 10px 16px 10px 40px; border: 2px solid #edf2f7; border-radius: 12px; font-size: 0.95rem; outline: none; transition: border-color 0.2s ease; background: #f8fafc; color: #2d3748; } .search-input:focus { border-color: #cbd5e0; background: #ffffff; } .search-input::placeholder { color: #a0aec0; } .search-icon { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: #a0aec0; } .clear-history-button { display: flex; align-items: center; gap: 8px; padding: 10px 16px; background: #fff5f5; border: none; border-radius: 12px; color: #e53e3e; cursor: pointer; transition: background 0.2s; font-size: 0.9rem; font-weight: 500; } .clear-history-button:hover { background: #fed7d7; } .history-content { padding: 32px; overflow-y: auto; flex: 1; background: #f8fafc; } .history-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 24px; margin: 0; } .history-item { background: #ffffff; border-radius: 16px; overflow: hidden; transition: transform 0.2s ease; cursor: pointer; border: 1px solid #edf2f7; display: flex; flex-direction: column; } .history-item:hover { transform: translateY(-2px); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.05); } .history-item-image { width: 100%; height: 220px; flex-shrink: 0; position: relative; border-radius: 8px 8px 0 0; overflow: hidden; background: #f7fafc; } .history-item-image img { width: 100%; height: 100%; object-fit: cover; } .history-item-info { padding: 16px; display: flex; flex-direction: column; gap: 12px; background: #ffffff; flex: 1; min-height: 0; } .history-metadata { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; } .history-timestamp { display: flex; align-items: center; gap: 4px; color: #718096; font-size: 0.85rem; } .history-status { display: flex; align-items: center; gap: 4px; font-size: 0.85rem; font-weight: 500; } .history-status.private { color: #e53e3e; } .history-status.public { color: #38a169; } .history-item-prompt-container { background: #ebf8ff; padding: 12px; border-radius: 8px; margin-top: 4px; max-height: calc(1.5em * 2 + 24px); overflow: hidden; } .history-item-prompt { color: #2b6cb0; font-size: 0.95rem; line-height: 1.5; margin: 0; font-weight: 600; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; max-height: calc(1.5em * 2); } .empty-state { text-align: center; padding: 48px; color: #718096; } .empty-state svg { width: 48px; height: 48px; margin-bottom: 16px; color: #a0aec0; } .empty-state p { margin: 8px 0; font-size: 0.95rem; } .empty-state p:first-of-type { font-weight: 600; color: #4a5568; font-size: 1.1rem; } .privacy-toggle { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 12px; padding: 0 4px; width: 100%; } .privacy-toggle button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; width: 100%; padding: 12px; border: none; border-radius: 6px; font-size: 0.95rem; font-weight: 500; cursor: pointer; transition: all 0.2s ease; color: #4a5568; } .privacy-toggle:hover { background: rgba(0, 0, 0, 0.05); } .privacy-toggle.public button { background: #2563eb; color: white; } .privacy-toggle.public button:hover { background: #1d4ed8; } .privacy-toggle.private button { background: #dc2626; color: white; } .privacy-toggle.private button:hover { background: #b91c1c; } .metadata-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.75); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); z-index: 10000; display: flex; justify-content: center; align-items: center; padding: 20px; } .metadata-content { background: #ffffff; border-radius: 16px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2); width: 90%; max-width: 1200px; max-height: 90vh; overflow-y: auto; position: relative; padding: 32px; } .metadata-close { position: absolute; top: 24px; right: 24px; background: none; border: none; color: var(--foreground); cursor: pointer; padding: 8px; border-radius: 50%; display: flex; align-items: center; justify-content: center; transition: background-color 0.2s; } .metadata-close:hover { background: rgba(var(--background-rgb), 0.1); } .metadata-grid { display: grid; grid-template-columns: 400px 1fr; gap: 32px; margin-bottom: 32px; } .metadata-image { background: var(--background-secondary, #f3f4f6); border-radius: 12px; padding: 16px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); } .metadata-image img { width: 100%; height: auto; border-radius: 8px; cursor: pointer; } .metadata-image img:hover { transform: scale(1.02); } .metadata-details { display: flex; flex-direction: column; gap: 24px; } .metadata-details h2 { margin: 0; font-size: 28px; font-weight: 600; color: var(--foreground); } .metadata-info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; } .metadata-info-item { background: var(--background-secondary, #f3f4f6); padding: 12px; border-radius: 8px; display: flex; flex-direction: column; gap: 4px; } .metadata-info-label { color: var(--foreground-secondary); font-size: 14px; display: flex; align-items: center; gap: 6px; } .metadata-info-value { color: var(--foreground); font-size: 16px; font-weight: 500; } .metadata-run-link { display: inline-flex; align-items: center; gap: 8px; padding: 12px 20px; background: var(--accent); color: white; border-radius: 8px; text-decoration: none; font-weight: 500; transition: background 0.2s; width: fit-content; } .metadata-run-link:hover { background: var(--accent-hover); } .metadata-section { margin-top: 32px; background: #ffffff; border-radius: 12px; padding: 24px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); } .metadata-section h3 { margin: 0 0 20px 0; font-size: 20px; font-weight: 600; color: var(--foreground); } .metadata-node-outputs { display: flex; flex-direction: column; gap: 16px; } .metadata-node { background: var(--background-secondary, #f3f4f6); border-radius: 8px; padding: 16px; } .metadata-node-title { font-weight: 500; color: var(--foreground); margin-bottom: 8px; } .metadata-node-content { font-family: monospace; white-space: pre-wrap; background: var(--background-tertiary, #e5e7eb); padding: 12px; border-radius: 6px; font-size: 14px; color: var(--foreground-secondary); } .metadata-images-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px; } .metadata-image-item { background: var(--background-secondary, #f3f4f6); padding: 12px; border-radius: 8px; transition: transform 0.2s; cursor: pointer; } .metadata-image-item:hover { transform: scale(1.02); } .metadata-image-item img { width: 100%; height: auto; border-radius: 4px; } .metadata-collapsible { margin-bottom: 16px; } .metadata-collapsible-header { width: 100%; padding: 12px; background: var(--background-secondary, #f3f4f6); border: none; border-radius: 8px; color: var(--foreground); font-weight: 500; cursor: pointer; display: flex; justify-content: space-between; align-items: center; } .metadata-collapsible-icon { transition: transform 0.2s; } .metadata-collapsible-content { display: none; padding: 16px; background: var(--background-tertiary, #e5e7eb); border-radius: 8px; margin-top: 8px; } .metadata-collapsible-content pre { margin: 0; white-space: pre-wrap; font-family: monospace; font-size: 14px; color: var(--foreground-secondary); } .batch-results-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.75); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); z-index: 10000; display: flex; justify-content: center; align-items: center; padding: 20px; } .batch-results-panel { background: var(--background); border-radius: 20px; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); width: 90%; max-width: 1200px; max-height: 90vh; overflow: hidden; display: flex; flex-direction: column; } .batch-results-header { display: flex; justify-content: space-between; align-items: center; padding: 24px 32px; border-bottom: 1px solid var(--border-color); background: var(--background); position: sticky; top: 0; z-index: 1; } .batch-results-content { padding: 32px; overflow-y: auto; flex: 1; } .batch-results-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 24px; margin: 0; } .batch-result-item { background: var(--background); border-radius: 16px; overflow: hidden; transition: all 0.3s ease; cursor: pointer; border: 1px solid var(--border-color); display: flex; flex-direction: column; } .batch-result-item:hover { transform: translateY(-4px); box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1); border-color: var(--accent-color); } .batch-result-item.success { border-color: var(--success-color); } .batch-result-item.error { border-color: var(--error-color); } .batch-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.75); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); z-index: 10000; display: flex; justify-content: center; align-items: center; padding: 32px; } .batch-panel { background: #ffffff; border-radius: 20px; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); width: 90%; max-width: 1000px; max-height: 90vh; overflow: hidden; display: flex; flex-direction: column; position: relative; } .batch-header { display: flex; justify-content: space-between; align-items: center; padding: 24px 32px; border-bottom: 1px solid #e5e7eb; background: #ffffff; position: sticky; top: 0; z-index: 1; } .batch-title-section { display: flex; align-items: center; gap: 12px; } .batch-title { font-size: 1.5rem; font-weight: 600; color: #1f2937; margin: 0; display: flex; align-items: center; gap: 12px; } .batch-title svg { width: 24px; height: 24px; color: #6366f1; } .batch-close-button { position: absolute; top: 20px; right: 20px; background: transparent; border: none; color: #6b7280; cursor: pointer; padding: 8px; border-radius: 8px; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; } .batch-close-button svg { width: 20px; height: 20px; } .batch-close-button:hover { background: #f3f4f6; color: #4b5563; } .batch-controls { display: flex; align-items: center; gap: 12px; } .batch-control-button { display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px; border-radius: 6px; border: none; font-weight: 500; cursor: pointer; transition: all 0.2s ease; } .batch-control-button svg { width: 16px; height: 16px; flex-shrink: 0; } .batch-control-button span { line-height: 1; } .batch-control-button:hover { background: #f5f5ff; border-color: #6366f1; color: #6366f1; } .batch-input-container { display: flex; flex-direction: column; gap: 16px; background: #ffffff; border-radius: 12px; padding: 24px; border: 1px solid #e5e7eb; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); } .batch-input-row { display: grid; grid-template-columns: 1fr auto; gap: 16px; padding: 16px; border-radius: 12px; background: #f9fafb; border: 1px solid #e5e7eb; transition: all 0.2s ease; } .batch-input-row:hover { border-color: #6366f1; background: #ffffff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .batch-input-fields { display: flex; flex-direction: row; gap: 12px; flex: 1; } .batch-input { flex: 1; min-width: 0; padding: 10px 12px; border: 1px solid #e5e7eb; border-radius: 8px; background: #ffffff; color: #1f2937; font-size: 0.95rem; line-height: 1.4; transition: all 0.2s ease; } .batch-input:focus { outline: none; border-color: #6366f1; box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); } .batch-input::placeholder { color: #9ca3af; } .batch-row-actions { display: flex; gap: 8px; align-items: center; margin-right: 4px; } .batch-row-action-button { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0; border: 1px solid #e5e7eb; border-radius: 6px; background: #ffffff; color: #6b7280; cursor: pointer; transition: all 0.2s ease; } .batch-row-action-button svg { width: 16px; height: 16px; } .batch-row-action-button:hover { background: #f3f4f6; border-color: #6366f1; color: #6366f1; } .batch-row-action-button.delete:hover { background: #fee2e2; border-color: #ef4444; color: #dc2626; } .batch-generate-button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; margin-top: 24px; padding: 14px 28px; background: linear-gradient(180deg, #ff5733 0%, #c70039 100%); color: white; border: none; border-radius: 10px; font-size: 1rem; font-weight: 600; cursor: pointer; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); letter-spacing: 0.5px; } .batch-generate-button:hover { background: linear-gradient(180deg, #c70039 0%, #900c3f 100%); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); transform: translateY(-2px); } .batch-generate-button:active { transform: translateY(0px); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4); } .batch-generate-button:disabled { background: #e5e7eb; cursor: not-allowed; transform: none; box-shadow: none; } .batch-generate-button:disabled { background: #e5e7eb; cursor: not-allowed; transform: none; box-shadow: none; } .batch-generate-button.processing { background: #818cf8; } .batch-generate-button.success { background: #10b981; } .batch-generate-button.error { background: #ef4444; } /* Action Buttons */ .action-button { display: flex; align-items: center; gap: 8px; padding: 12px 20px; border-radius: 12px; font-size: 1rem; font-weight: 500; cursor: pointer; transition: all 0.2s ease; border: 1px solid var(--border-color); background: var(--background); color: var(--foreground); } .action-button svg { width: 20px; height: 20px; } .action-button:hover { background: var(--background-secondary); border-color: var(--accent-color); } .action-button.primary { background: var(--accent-color); color: white; border: none; } .action-button.primary:hover { background: var(--accent-color-hover); transform: translateY(-1px); } /* Header Actions */ .header-actions { display: flex; align-items: center; gap: 16px; } .private-toggle { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 8px 16px; border-radius: 12px; font-size: 1rem; font-weight: 500; cursor: pointer; transition: all 0.2s ease; border: 1px solid var(--border-color); background: var(--background); color: var(--foreground); } .private-toggle svg { width: 20px; height: 20px; } .private-toggle:hover { background: var(--background-secondary); border-color: var(--accent-color); } .private-toggle.active { background: var(--accent-color); color: white; border: none; } /* Add Row Button */ .add-row-button { display: flex; align-items: center; gap: 8px; padding: 16px; border-radius: 16px; font-size: 1.1rem; font-weight: 500; cursor: pointer; transition: all 0.2s ease; width: 100%; justify-content: center; margin-top: 16px; } .add-row-button svg { width: 24px; height: 24px; } .add-row-button:hover { border-color: var(--accent-color); color: var(--accent-color); background: var(--background-secondary); } .input-section { margin-bottom: 24px; } .input-label { display: block; font-size: 1rem; font-weight: 600; color: var(--foreground); margin-bottom: 12px; } .input-field { display: flex; align-items: center; gap: 16px; background: var(--background); border: 1px solid var(--border-color); border-radius: 16px; padding: 12px 16px; transition: all 0.2s ease; } .input-field:hover { border-color: var(--accent-color); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); } .input-fields-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } .history-input { width: 100%; background: transparent; border: none; color: var(--foreground); font-size: 1rem; padding: 8px; border-radius: 8px; } .history-input:focus { outline: none; background: var(--background-secondary); } .history-input::placeholder { color: var(--foreground-secondary); opacity: 0.7; } .batch-row-actions button { padding: 8px; border-radius: 8px; color: var(--foreground-secondary); transition: all 0.2s ease; } .batch-row-actions button:hover { background: var(--background-secondary); color: var(--foreground); } .add-row-button { width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 16px; background: transparent; border: 2px dashed var(--border-color); border-radius: 16px; color: var(--foreground-secondary); font-size: 1rem; font-weight: 500; cursor: pointer; transition: all 0.2s ease; margin-top: 24px; } .add-row-button:hover { border-color: var(--accent-color); color: var(--accent-color); background: var(--background-secondary); } .batch-controls { margin-top: 32px; padding: 24px; background: var(--background-secondary); border-radius: 20px; display: flex; align-items: center; gap: 24px; } .batch-generate-button { background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%); color: white; padding: 12px 24px; border-radius: 8px; border: none; font-weight: 600; margin-top: 16px; width: 100%; cursor: pointer; box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); letter-spacing: 0.5px; } .batch-generate-button:hover { background: linear-gradient(135deg, #4f46e5 0%, #4338ca 100%); box-shadow: 0 6px 16px rgba(79, 70, 229, 0.4); transform: translateY(-2px); } .batch-generate-button:active { transform: translateY(0px); box-shadow: 0 2px 8px rgba(79, 70, 229, 0.4); } .batch-generate-button:disabled { background: #e5e7eb; cursor: not-allowed; transform: none; box-shadow: none; } .batch-results-container { padding: 2rem; background: white; border-radius: 12px; max-width: 1200px; margin: 0 auto; } .batch-results-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1.5rem; margin-bottom: 2rem; } .batch-result-item { background: white; border-radius: 16px; overflow: hidden; transition: all 0.3s ease; cursor: pointer; border: 1px solid #e5e7eb; display: flex; flex-direction: column; } .result-image-wrapper { position: relative; padding-top: 100%; background: #f3f4f6; overflow: hidden; } .result-image { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; transition: transform 0.3s ease; } .result-image:hover { transform: scale(1.05); } .result-details { padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem; } .result-prompt { font-size: 0.875rem; color: #1f2937; font-weight: 500; line-height: 1.25rem; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; } .result-prompt:hover { -webkit-line-clamp: unset; } .result-metadata { display: flex; align-items: center; gap: 0.75rem; font-size: 0.75rem; color: #6b7280; } .final-image-popup { position: fixed; bottom: 20px; right: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); z-index: 10000; padding: 10px; max-width: 300px; animation: slideIn 0.3s ease-out; } .final-image-popup .popup-content { display: flex; flex-direction: column; gap: 10px; } .final-image-popup img { width: 100%; height: auto; border-radius: 4px; } .final-image-popup .open-new-tab { background: #007bff; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background 0.2s; } .final-image-popup .open-new-tab:hover { background: #0056b3; } @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } /* Bug Report Form Styles */ .glif-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); display: flex; justify-content: center; align-items: center; z-index: 10000; backdrop-filter: blur(4px); } .glif-modal-content { background: #ffffff; padding: 24px; border-radius: 12px; width: 90%; max-width: 500px; max-height: 90vh; overflow-y: auto; position: relative; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); } .glif-close-button { position: absolute; top: 16px; right: 16px; background: none; border: none; cursor: pointer; color: #6b7280; padding: 8px; border-radius: 8px; transition: all 0.2s; line-height: 0; } .glif-close-button:hover { background: #f3f4f6; color: #1f2937; } .glif-modal-title { margin: 0 0 20px 0; font-size: 20px; font-weight: 600; color: #1f2937; } .glif-type-container { display: flex; gap: 8px; margin-bottom: 20px; } .glif-type-button { flex: 1; padding: 10px; border: 1px solid #e5e7eb; border-radius: 8px; background: #ffffff; cursor: pointer; font-size: 14px; font-weight: 500; color: #6b7280; transition: all 0.2s; display: flex; align-items: center; justify-content: center; gap: 6px; } .glif-type-button:hover { border-color: #3b82f6; color: #3b82f6; background: #f3f4f6; } .glif-type-button.active { border-color: #3b82f6; color: #3b82f6; background: #eff6ff; } .glif-input-container { margin-bottom: 16px; } .glif-input-label { display: block; margin-bottom: 6px; font-weight: 500; color: #374151; font-size: 14px; } .glif-input { width: 100%; padding: 10px 12px; border: 1px solid #e5e7eb; border-radius: 8px; font-size: 14px; transition: all 0.2s; background: #f9fafb; color: #1f2937; } .glif-textarea { height: 120px; resize: vertical; line-height: 1.5; } .glif-input:focus { outline: none; border-color: #3b82f6; background: #ffffff; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); } .glif-submit-button { width: 100%; padding: 10px; background: #3b82f6; color: white; border: none; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s; display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 8px; } .glif-submit-button:hover { background: #2563eb; } .glif-submit-button:disabled { background: #93c5fd; cursor: not-allowed; } /* Toast Notification Styles */ .glif-toast { position: fixed; bottom: 24px; right: 24px; padding: 12px 16px; border-radius: 8px; font-size: 14px; font-weight: 500; color: white; z-index: 10001; display: flex; align-items: center; gap: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); animation: slideInRight 0.3s ease-out; } .glif-toast.success { background: #059669; } .glif-toast.error { background: #dc2626; } @keyframes slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } .glif-tools-menu-item svg, .bug-report-type-button svg { width: 20px; height: 20px; min-width: 20px; flex-shrink: 0; } /* Bug Report Panel Specific Styles */ [data-glif-dark-mode="true"] .glif-modal-content { background-color: #1E1E1E !important; color: #FFFFFF !important; } [data-glif-dark-mode="true"] .glif-modal-title { color: #FFFFFF !important; } [data-glif-dark-mode="true"] .glif-type-button { background-color: #2A2A2A !important; color: #FFFFFF !important; border: 1px solid #3E3E3E !important; transition: all 0.2s ease !important; } [data-glif-dark-mode="true"] .glif-type-button:hover { background-color: #33363C !important; border-color: #4F46E5 !important; } [data-glif-dark-mode="true"] .glif-type-button.active { background-color: #4F46E5 !important; border-color: #4F46E5 !important; color: #FFFFFF !important; } [data-glif-dark-mode="true"] .glif-input-label { color: #FFFFFF !important; } [data-glif-dark-mode="true"] .glif-input, [data-glif-dark-mode="true"] .glif-textarea { background-color: #2A2A2A !important; color: #FFFFFF !important; border: 1px solid #3E3E3E !important; } [data-glif-dark-mode="true"] .glif-input:focus, [data-glif-dark-mode="true"] .glif-textarea:focus { border-color: #4F46E5 !important; box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.2) !important; } [data-glif-dark-mode="true"] .glif-input::placeholder, [data-glif-dark-mode="true"] .glif-textarea::placeholder { color: #6B7280 !important; } [data-glif-dark-mode="true"] .glif-submit-button { background-color: #4F46E5 !important; color: #FFFFFF !important; border: none !important; transition: all 0.2s ease !important; } [data-glif-dark-mode="true"] .glif-submit-button:hover { background-color: #4338CA !important; } [data-glif-dark-mode="true"] .glif-close-button { color: #6B7280 !important; } [data-glif-dark-mode="true"] .glif-close-button:hover { color: #FFFFFF !important; } /* Batch Generator Specific Styles */ [data-glif-dark-mode="true"] #batchInputContainer .batch-input-row { background-color: #2A2A2A !important; border: 1px solid #3E3E3E !important; color: #FFFFFF !important; } [data-glif-dark-mode="true"] #batchInputContainer .batch-input-row:hover { background-color: #33363C !important; border-color: #4F46E5 !important; } [data-glif-dark-mode="true"] #batchInputContainer .batch-input { background-color: #2A2A2A !important; color: #FFFFFF !important; border: 1px solid #3E3E3E !important; } [data-glif-dark-mode="true"] #batchInputContainer .batch-input:focus { border-color: #4F46E5 !important; box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.2) !important; } [data-glif-dark-mode="true"] #batchInputContainer .batch-input::placeholder { color: #6B7280 !important; } [data-glif-dark-mode="true"] #batchInputContainer .batch-row-action-button { color: #6B7280 !important; } [data-glif-dark-mode="true"] #batchInputContainer .batch-row-action-button:hover { color: #FFFFFF !important; } /* History Panel Dark Mode Styles */ [data-glif-dark-mode="true"] .history-filters { background-color: #1A1B1E !important; border-bottom: 1px solid #2D2E32 !important; padding: 16px !important; } [data-glif-dark-mode="true"] .history-filters input { background-color: #25262B !important; color: #E6E8EC !important; border: 1px solid #383A3F !important; transition: all 0.2s ease !important; } [data-glif-dark-mode="true"] .history-filters input:focus { border-color: #4F46E5 !important; background-color: #2C2D32 !important; box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.25) !important; } [data-glif-dark-mode="true"] .history-filters input::placeholder { color: #9095A0 !important; } [data-glif-dark-mode="true"] .history-item > div { background-color: #25262B !important; color: #E6E8EC !important; border: 1px solid #383A3F !important; transition: all 0.2s ease !important; } [data-glif-dark-mode="true"] .history-item > div:hover { background-color: #2C2D32 !important; border-color: #4F46E5 !important; transform: translateY(-1px) !important; } [data-glif-dark-mode="true"] .history-item-content { color: #E6E8EC !important; } [data-glif-dark-mode="true"] .history-item-metadata { color: #9095A0 !important; } [data-glif-dark-mode="true"] .history-item-prompt { color: #E6E8EC !important; font-weight: 500 !important; } [data-glif-dark-mode="true"] .history-item-timestamp { color: #9095A0 !important; font-size: 0.9em !important; } [data-glif-dark-mode="true"] .history-item-actions { background-color: #2C2D32 !important; border-top: 1px solid #383A3F !important; padding: 8px !important; } [data-glif-dark-mode="true"] .history-item-actions button { color: #9095A0 !important; transition: all 0.2s ease !important; } [data-glif-dark-mode="true"] .history-item-actions button:hover { color: #E6E8EC !important; background-color: #383A3F !important; } /* Metadata Panel Dark Mode */ [data-glif-dark-mode="true"] .metadata-grid { background-color: #25262B !important; border: 1px solid #383A3F !important; border-radius: 8px !important; } [data-glif-dark-mode="true"] .metadata-details { background-color: #2C2D32 !important; border-left: 1px solid #383A3F !important; } [data-glif-dark-mode="true"] .metadata-details h2 { color: #E6E8EC !important; font-weight: 600 !important; } [data-glif-dark-mode="true"] .metadata-info-grid { gap: 16px !important; } [data-glif-dark-mode="true"] .metadata-info-item { background-color: #1A1B1E !important; border: 1px solid #2D2E32 !important; border-radius: 6px !important; padding: 12px !important; } [data-glif-dark-mode="true"] .metadata-info-label { color: #9095A0 !important; font-weight: 500 !important; } [data-glif-dark-mode="true"] .metadata-info-value { color: #E6E8EC !important; font-family: 'Roboto Mono', monospace !important; } [data-glif-dark-mode="true"] .metadata-section { border-top: 1px solid #383A3F !important; padding-top: 24px !important; margin-top: 24px !important; } [data-glif-dark-mode="true"] .metadata-section h3 { color: #E6E8EC !important; font-weight: 600 !important; margin-bottom: 16px !important; } [data-glif-dark-mode="true"] .metadata-collapsible { background-color: #25262B !important; border: 1px solid #383A3F !important; border-radius: 6px !important; margin-bottom: 8px !important; } [data-glif-dark-mode="true"] .metadata-collapsible-header { background-color: #2C2D32 !important; color: #E6E8EC !important; padding: 12px 16px !important; font-weight: 500 !important; transition: all 0.2s ease !important; } [data-glif-dark-mode="true"] .metadata-collapsible-header:hover { background-color: #383A3F !important; } [data-glif-dark-mode="true"] .metadata-collapsible-content { background-color: #1A1B1E !important; border-top: 1px solid #2D2E32 !important; padding: 16px !important; } [data-glif-dark-mode="true"] .metadata-collapsible-content pre { color: #E6E8EC !important; font-family: 'Roboto Mono', monospace !important; font-size: 0.9em !important; } /* Batch Input Container Dark Mode */ [data-glif-dark-mode="true"] .batch-input-container { background-color: #1A1B1E !important; } [data-glif-dark-mode="true"] .batch-content { background-color: #1A1B1E !important; } /* History Item Specific Dark Mode */ [data-glif-dark-mode="true"] .history-item > div:nth-child(2) > div:nth-child(2) { background-color: #25262B !important; color: #E6E8EC !important; } /* Node Outputs Dark Mode */ [data-glif-dark-mode="true"] .metadata-node-outputs { background-color: #1A1B1E !important; border-radius: 8px !important; padding: 16px !important; } [data-glif-dark-mode="true"] .metadata-node { background-color: #25262B !important; border: 1px solid #383A3F !important; border-radius: 6px !important; margin-bottom: 12px !important; } [data-glif-dark-mode="true"] .metadata-node:last-child { margin-bottom: 0 !important; } [data-glif-dark-mode="true"] .metadata-node-title { background-color: #2C2D32 !important; color: #E6E8EC !important; font-weight: 500 !important; padding: 8px 12px !important; border-bottom: 1px solid #383A3F !important; border-radius: 6px 6px 0 0 !important; } [data-glif-dark-mode="true"] .metadata-node-content { color: #E6E8EC !important; font-family: 'Roboto Mono', monospace !important; font-size: 0.9em !important; padding: 12px !important; background-color: #1A1B1E !important; border-radius: 0 0 6px 6px !important; white-space: pre-wrap !important; } /* Metadata Sections Dark Mode */ [data-glif-dark-mode="true"] .metadata-section { border: 1px solid #2E2E2E !important; background-color: #1E1E1E !important; padding: 20px !important; border-radius: 8px !important; margin-top: 24px !important; } [data-glif-dark-mode="true"] .metadata-section h3 { color: #FFFFFF !important; margin-bottom: 16px !important; } [data-glif-dark-mode="true"] .metadata-node { background-color: #25262B !important; border: 1px solid #2E2E2E !important; } [data-glif-dark-mode="true"] .metadata-node-content { background-color: #2C2D32 !important; color: #E6E8EC !important; border: 1px solid #383A3F !important; } [data-glif-dark-mode="true"] .metadata-image-item { background-color: #25262B !important; border: 1px solid #2E2E2E !important; } [data-glif-dark-mode="true"] .metadata-collapsible-header { background-color: #25262B !important; color: #E6E8EC !important; border: 1px solid #2E2E2E !important; } [data-glif-dark-mode="true"] .metadata-collapsible-content { background-color: #2C2D32 !important; border: 1px solid #383A3F !important; } [data-glif-dark-mode="true"] .metadata-collapsible-content pre { color: #E6E8EC !important; } /* Batch Input Container Dark Mode */ [data-glif-dark-mode="true"] .batch-input-container { background-color: #1A1B1E !important; } [data-glif-dark-mode="true"] .batch-content { background-color: #1A1B1E !important; } /* History Item Specific Dark Mode */ [data-glif-dark-mode="true"] .history-item > div:nth-child(2) > div:nth-child(2) { background-color: #25262B !important; color: #E6E8EC !important; } /* Node Outputs Dark Mode */ [data-glif-dark-mode="true"] .metadata-node-outputs { background-color: #1A1B1E !important; border-radius: 8px !important; padding: 16px !important; } [data-glif-dark-mode="true"] .metadata-node { background-color: #25262B !important; border: 1px solid #383A3F !important; border-radius: 6px !important; margin-bottom: 12px !important; } [data-glif-dark-mode="true"] .metadata-node:last-child { margin-bottom: 0 !important; } [data-glif-dark-mode="true"] .metadata-node-title { background-color: #2C2D32 !important; color: #E6E8EC !important; font-weight: 500 !important; padding: 8px 12px !important; border-bottom: 1px solid #383A3F !important; border-radius: 6px 6px 0 0 !important; } [data-glif-dark-mode="true"] .metadata-node-content { color: #E6E8EC !important; font-family: 'Roboto Mono', monospace !important; font-size: 0.9em !important; padding: 12px !important; background-color: #1A1B1E !important; border-radius: 0 0 6px 6px !important; white-space: pre-wrap !important; } `; document.head.appendChild(styleSheet); }; // Create tools dropdown const createToolsDropdown = () => { const toolsDropdown = document.createElement('div'); toolsDropdown.className = 'glif-tools-dropdown'; const toolsButton = document.createElement('button'); toolsButton.className = 'flex items-center gap-1 text-lg font-bold hover:text-brand-600 active:text-brand-600'; toolsButton.innerHTML = `<span class="block h-2 w-2"></span>${icons.tools}<span>Tools</span>`; toolsDropdown.appendChild(toolsButton); const menu = document.createElement('div'); menu.className = 'glif-tools-menu'; const createMenuItem = (icon, text, onClick) => { const item = document.createElement('div'); item.className = 'glif-tools-menu-item'; item.innerHTML = `${icon}<span>${text}</span>`; item.addEventListener('click', (e) => { e.stopPropagation(); onClick(); menu.classList.remove('active'); }); return item; }; // Add menu items menu.appendChild(createMenuItem(icons.history, 'View History', displayHistoryPanel)); menu.appendChild(createMenuItem(icons.batch, 'Batch Generator', displayBatchPanel)); menu.appendChild(createMenuItem(icons.bug, 'Report Bug', displayBugReportForm)); // Create dark mode toggle item with an ID for easy updating const darkModeItem = createMenuItem( isDarkMode ? icons.sun : icons.moon, isDarkMode ? 'Light Mode' : 'Dark Mode', toggleDarkMode ); darkModeItem.id = 'dark-mode-toggle'; menu.appendChild(darkModeItem); // Add credits const credits = document.createElement('div'); credits.className = 'glif-tools-credits'; credits.innerHTML = 'Made by <a href="https://glif.app/@appelsiensam" target="_blank">I12bp8</a> <3'; menu.appendChild(credits); toolsDropdown.appendChild(menu); toolsButton.addEventListener('click', (e) => { e.stopPropagation(); menu.classList.toggle('active'); }); document.addEventListener('click', () => { menu.classList.remove('active'); }); return toolsDropdown; }; // Display bug report form function displayBugReportForm() { const modal = document.createElement('div'); modal.className = 'glif-modal'; const form = document.createElement('div'); form.className = 'glif-modal-content'; const closeButton = document.createElement('button'); closeButton.className = 'glif-close-button'; closeButton.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"></path></svg>`; closeButton.addEventListener('click', () => modal.remove()); const title = document.createElement('h2'); title.textContent = 'Report Bug / Request Feature'; title.className = 'glif-modal-title'; const typeContainer = document.createElement('div'); typeContainer.className = 'glif-type-container'; const createTypeButton = (text, type) => { const button = document.createElement('button'); button.innerHTML = `${icons[type]} ${text}`; button.dataset.type = type; button.className = 'glif-type-button'; button.addEventListener('click', () => { typeContainer.querySelectorAll('button').forEach(btn => btn.classList.remove('active')); button.classList.add('active'); selectedType = type; }); return button; }; let selectedType = 'bug'; const bugButton = createTypeButton('Report Bug', 'bug'); const featureButton = createTypeButton('Request Feature', 'feature'); bugButton.classList.add('active'); typeContainer.appendChild(bugButton); typeContainer.appendChild(featureButton); const createInput = (label, placeholder, isTextarea = false) => { const container = document.createElement('div'); container.className = 'glif-input-container'; const labelEl = document.createElement('label'); labelEl.textContent = label; labelEl.className = 'glif-input-label'; const input = document.createElement(isTextarea ? 'textarea' : 'input'); input.placeholder = placeholder; input.className = `glif-input ${isTextarea ? 'glif-textarea' : ''}`; container.appendChild(labelEl); container.appendChild(input); return { container, input }; }; const titleInput = createInput('Title', 'Brief description of the bug/feature'); const descriptionInput = createInput('Description', 'Detailed explanation...', true); const submitButton = document.createElement('button'); submitButton.textContent = 'Submit'; submitButton.className = 'glif-submit-button'; submitButton.addEventListener('click', async () => { const title = titleInput.input.value.trim(); const description = descriptionInput.input.value.trim(); if (!title || !description) { showToast('Please fill in all fields', 'error'); return; } submitButton.disabled = true; submitButton.innerHTML = `${icons.loading} Submitting...`; try { const response = await fetch('https://discord.com/api/webhooks/1313174668378771568/MESzfXqFIZVhUQKK70EavPTDTV6iW8ZuW6yPlAUi1ugPYU7tZm9-pThCZy9rF-VPwQeY', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ embeds: [{ title: `${selectedType === 'bug' ? '🐛 Bug Report' : '✨ Feature Request'}: ${title}`, description: description, color: selectedType === 'bug' ? 15548997 : 5793266, footer: { text: `Submitted via GLIF Tools v${GM_info.script.version}` }, timestamp: new Date().toISOString() }] }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } showToast('Submitted successfully!', 'success'); setTimeout(() => modal.remove(), 1500); } catch (error) { console.error('Error submitting form:', error); showToast('Failed to submit. Please try again.', 'error'); submitButton.disabled = false; submitButton.innerHTML = 'Submit'; } }); form.append(closeButton, title, typeContainer, titleInput.container, descriptionInput.container, submitButton); modal.appendChild(form); document.body.appendChild(modal); } // Filter images function const filterImages = (filter) => { const grid = document.querySelector('.history-grid'); if (!grid) return; Array.from(grid.children).forEach(item => { if (item.classList.contains('empty-state')) return; const isPrivate = item.dataset.private === 'true'; item.style.display = filter === 'all' || (filter === 'private' && isPrivate) || (filter === 'public' && !isPrivate) ? 'block' : 'none'; }); }; // Process stream response to save images async function processStreamResponse(response, isPrivate, inputs) { const reader = response.body.getReader(); let finalImageUrl = null; let nodeOutputs = {}; let graphExecutionState = null; let spellRun = null; let rawResponse = []; let allImageUrls = new Set(); let lastNodeWithImage = null; let spellDetails = null; let nodeHistory = []; try { while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = new TextDecoder().decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const jsonStr = line.slice(6); if (jsonStr.trim()) { try { const data = JSON.parse(jsonStr); rawResponse.push(data); if (data.type === 'image' && data.url) { finalImageUrl = data.url; } // Extract spell details if (data.spellRun && !spellDetails) { spellDetails = { spellId: data.spellRun.spellId, spellName: data.spellRun.spell?.name, startedAt: data.spellRun.startedAt, completedAt: data.spellRun.completedAt, totalDuration: data.spellRun.totalDuration, outputImageWidth: data.spellRun.outputImageWidth, outputImageHeight: data.spellRun.outputImageHeight, inputs: data.spellRun.inputs }; } if (data.graphExecutionState) { graphExecutionState = data.graphExecutionState; Object.entries(data.graphExecutionState.nodes).forEach(([nodeName, nodeData]) => { if (nodeData.output) { nodeOutputs[nodeName] = nodeData.output; if (nodeData.output.type === 'IMAGE') { allImageUrls.add(nodeData.output.value); lastNodeWithImage = nodeData.output.value; const isOutputNode = !Object.values(data.graphExecutionState.nodes).some(node => node.inputs && Object.values(node.inputs).some(input => input.connectionId && input.connectionId.startsWith(nodeName) ) ); if (isOutputNode) { finalImageUrl = nodeData.output.value; } } } }); } } catch (e) { console.error('Error parsing JSON:', e); } } } } } const displayImageUrl = finalImageUrl || lastNodeWithImage || Array.from(allImageUrls).pop(); if (!displayImageUrl) { throw new Error('No image generated'); } // Only show popup for private runs from the original form if (isPrivate && inputs.isFromForm) { showImagePopup(displayImageUrl); } // Get prompt from inputs object - now using the 'value' property let prompt = 'No prompt available'; if (inputs && inputs.value) { prompt = inputs.value; } const historyEntry = { url: displayImageUrl, timestamp: new Date().toISOString(), isPrivate: isPrivate, prompt: prompt, nodeOutputs, graphExecutionState, spellDetails, nodeHistory, rawResponse: rawResponse.length > 0 ? rawResponse : undefined, allImageUrls: Array.from(allImageUrls) }; console.log('Saving history entry:', historyEntry); saveToHistory(historyEntry); } catch (error) { console.error('Error processing stream:', error); } } // Create history item const createHistoryItem = (entry) => { const item = document.createElement('div'); item.className = 'history-item'; item.dataset.private = entry.isPrivate; const imageContainer = document.createElement('div'); imageContainer.className = 'history-item-image'; imageContainer.innerHTML = `<img src="${entry.url}" alt="Generated Image" loading="lazy">`; const info = document.createElement('div'); info.className = 'history-item-info'; const metadata = document.createElement('div'); metadata.className = 'history-metadata'; const timestamp = document.createElement('div'); timestamp.className = 'history-timestamp'; timestamp.innerHTML = `${icons.time} ${new Date(entry.timestamp).toLocaleString()}`; const status = document.createElement('div'); status.className = `history-status ${entry.isPrivate ? 'private' : 'public'}`; status.innerHTML = entry.isPrivate ? `${icons.private} Private` : `${icons.globe} Public`; metadata.appendChild(timestamp); metadata.appendChild(status); const promptContainer = document.createElement('div'); promptContainer.className = 'history-item-prompt-container'; const prompt = document.createElement('div'); prompt.className = 'history-item-prompt'; prompt.textContent = entry.prompt || 'No prompt available'; promptContainer.appendChild(prompt); info.appendChild(metadata); info.appendChild(promptContainer); item.appendChild(imageContainer); item.appendChild(info); item.addEventListener('click', () => displayMetadata(entry)); return item; }; // Display metadata overlay const displayMetadata = (entry) => { const overlay = document.createElement('div'); overlay.className = 'metadata-overlay'; overlay.innerHTML = ` <div class="metadata-content"> <button class="metadata-close">${icons.trash}</button> <div class="metadata-grid"> <div class="metadata-image"> <img src="${entry.url}" alt="Generated Image"> </div> <div class="metadata-details"> <h2>${entry.spellName || 'Image Details'}</h2> <div class="metadata-info-grid"> ${[ { icon: '🪄', label: 'Spell ID', value: entry.spellId || 'N/A' }, { icon: '🔄', label: 'Run ID', value: entry.runId || 'N/A' }, { icon: '🕒', label: 'Created', value: new Date(entry.timestamp).toLocaleString() }, { icon: entry.isPrivate ? '🔒' : '🌐', label: 'Privacy', value: entry.isPrivate ? 'Private' : 'Public' }, { icon: '💻', label: 'Client', value: entry.clientType || 'N/A' }, { icon: '👤', label: 'User', value: entry.user?.name || 'N/A' } ].map(info => ` <div class="metadata-info-item"> <div class="metadata-info-label"> <span class="metadata-info-icon">${info.icon}</span> ${info.label} </div> <div class="metadata-info-value">${info.value}</div> </div> `).join('')} </div> ${entry.runId && entry.user?.name ? ` <a href="https://glif.app/@${entry.user.name}/runs/${entry.runId}" target="_blank" class="metadata-run-link"> View Run Details ${icons.search} </a> ` : ''} </div> </div> ${entry.nodeOutputs && Object.keys(entry.nodeOutputs).length > 0 ? ` <div class="metadata-section"> <h3>🔧 Node Outputs</h3> <div class="metadata-node-outputs"> ${Object.entries(entry.nodeOutputs).map(([key, value]) => ` <div class="metadata-node"> <div class="metadata-node-title">${key}</div> <div class="metadata-node-content">${JSON.stringify(value, null, 2)}</div> </div> `).join('')} </div> </div> ` : ''} ${entry.allImageUrls && entry.allImageUrls.length > 0 ? ` <div class="metadata-section"> <h3>🖼️ All Generated Images</h3> <div class="metadata-images-grid"> ${entry.allImageUrls.map(url => ` <div class="metadata-image-item"> <img src="${url}" alt="Generated Image" onclick="window.open('${url}', '_blank')"> </div> `).join('')} </div> </div> ` : ''} ${(entry.graphExecutionState || entry.rawResponse) ? ` <div class="metadata-section"> <h3>⚙️ Technical Details</h3> ${entry.graphExecutionState ? ` <div class="metadata-collapsible"> <button class="metadata-collapsible-header"> Graph Execution State <span class="metadata-collapsible-icon">▼</span> </button> <div class="metadata-collapsible-content"> <pre>${JSON.stringify(entry.graphExecutionState, null, 2)}</pre> </div> </div> ` : ''} ${entry.rawResponse ? ` <div class="metadata-collapsible"> <button class="metadata-collapsible-header"> Raw Response Data <span class="metadata-collapsible-icon">▼</span> </button> <div class="metadata-collapsible-content"> <pre>${JSON.stringify(entry.rawResponse, null, 2)}</pre> </div> </div> ` : ''} </div> ` : ''} </div> `; // Add click handlers for collapsible sections overlay.querySelectorAll('.metadata-collapsible-header').forEach(header => { header.addEventListener('click', () => { const content = header.nextElementSibling; const icon = header.querySelector('.metadata-collapsible-icon'); const isOpen = content.style.display === 'block'; content.style.display = isOpen ? 'none' : 'block'; icon.style.transform = isOpen ? 'rotate(0deg)' : 'rotate(180deg)'; }); }); // Close on overlay click overlay.addEventListener('click', (e) => { if (e.target === overlay) { overlay.remove(); } }); // Close button handler overlay.querySelector('.metadata-close').addEventListener('click', () => { overlay.remove(); }); document.body.appendChild(overlay); }; // Display history panel const displayHistoryPanel = () => { const existingPanel = document.getElementById('glifHistoryPanel'); if (existingPanel) { existingPanel.remove(); } const overlay = document.createElement('div'); overlay.className = 'history-overlay'; overlay.addEventListener('click', (e) => { if (e.target === overlay) { overlay.remove(); } }); const panel = document.createElement('div'); panel.className = 'history-panel'; panel.id = 'glifHistoryPanel'; const header = document.createElement('div'); header.className = 'history-header'; const titleSection = document.createElement('div'); titleSection.className = 'history-panel-title-section'; const title = document.createElement('h2'); title.className = 'history-panel-title'; title.innerHTML = `${icons.history}<span>Image History</span>`; const clearButton = document.createElement('button'); clearButton.className = 'clear-history-button'; clearButton.innerHTML = `${icons.trash}<span>Clear All</span>`; clearButton.addEventListener('click', (e) => { e.stopPropagation(); if (confirm('Are you sure you want to delete all saved images? This action cannot be undone.')) { GM_setValue('imageHistory', []); overlay.remove(); displayHistoryPanel(); } }); titleSection.appendChild(title); titleSection.appendChild(clearButton); const controls = document.createElement('div'); controls.className = 'history-controls'; const filters = document.createElement('div'); filters.className = 'history-filters'; ['All', 'Private', 'Public'].forEach(filterText => { const button = document.createElement('button'); button.className = `filter-button${filterText === 'All' ? ' active' : ''}`; button.textContent = filterText; button.addEventListener('click', (e) => { e.stopPropagation(); filters.querySelectorAll('.filter-button').forEach(btn => btn.classList.remove('active')); button.classList.add('active'); filterImages(filterText.toLowerCase()); }); filters.appendChild(button); }); const searchContainer = document.createElement('div'); searchContainer.className = 'search-container'; const searchIcon = document.createElement('div'); searchIcon.className = 'search-icon'; searchIcon.innerHTML = icons.search; const searchInput = document.createElement('input'); searchInput.className = 'search-input'; searchInput.type = 'text'; searchInput.placeholder = 'Search prompts...'; searchInput.addEventListener('input', (e) => { const searchTerm = e.target.value.toLowerCase(); const items = document.querySelectorAll('.history-item'); items.forEach(item => { const prompt = item.querySelector('.history-item-prompt')?.textContent.toLowerCase() || ''; item.style.display = prompt.includes(searchTerm) ? 'block' : 'none'; }); }); searchContainer.appendChild(searchIcon); searchContainer.appendChild(searchInput); controls.appendChild(filters); controls.appendChild(searchContainer); header.appendChild(titleSection); header.appendChild(controls); const content = document.createElement('div'); content.className = 'history-content'; const history = GM_getValue('imageHistory', []); if (history.length === 0) { const emptyState = document.createElement('div'); emptyState.className = 'empty-state'; emptyState.innerHTML = ` ${icons.image} <p>No images yet</p> <p>Images will appear here as you generate them</p> `; content.appendChild(emptyState); } else { const grid = document.createElement('div'); grid.className = 'history-grid'; history.forEach(entry => { grid.appendChild(createHistoryItem(entry)); }); content.appendChild(grid); } panel.appendChild(header); panel.appendChild(content); overlay.appendChild(panel); document.body.appendChild(overlay); }; // Save to history const saveToHistory = (entry) => { const history = GM_getValue('imageHistory', []); const isDuplicate = history.some(item => item.url === entry.url && item.timestamp === entry.timestamp ); if (!isDuplicate) { history.unshift(entry); if (history.length > 100) history.pop(); GM_setValue('imageHistory', history); } }; // Replace fetch const originalFetch = unsafeWindow.fetch; unsafeWindow.fetch = async (...args) => { const [url, options] = args; if (url.includes('/api/run-glif')) { const modifiedOptions = {...options}; const body = JSON.parse(modifiedOptions.body); const isPrivate = GM_getValue('isPrivate', false); // Set private/public mode body.glifRunIsPublic = !isPrivate; modifiedOptions.body = JSON.stringify(body); const response = await originalFetch(url, modifiedOptions); const clonedResponse = response.clone(); // Always use the first value from inputs object const firstValue = Object.values(body.inputs)[0]; // Create a simple object with the first value const inputsObj = { value: firstValue, isFromForm: true // Flag to indicate this is from the original form }; // Process the response stream processStreamResponse(clonedResponse, isPrivate, inputsObj).catch(err => { console.error('Error processing response:', err); }); return response; } return originalFetch(...args); }; // Get workflow inputs function getWorkflowInputs() { const form = document.querySelector('form'); if (!form) return []; const inputs = []; form.querySelectorAll('input[type="text"], input[type="number"], textarea').forEach(input => { if (input.name && !input.name.startsWith('__') && input.name !== 'spellId' && input.name !== 'version') { // Find the label for this input let label = ''; const labelElement = form.querySelector(`label[for="${input.id}"]`); if (labelElement) { label = labelElement.textContent.trim(); } else { // Try to find a label that contains this input const parentLabel = input.closest('label'); if (parentLabel) { label = parentLabel.textContent.trim(); } } inputs.push({ name: input.name, type: input.type, label: label || input.name, // Use label if found, otherwise use name placeholder: label || input.name }); } }); return inputs; } async function generateImage(input) { const isPrivate = GM_getValue('isPrivate', true); // Get spell ID from URL or input const spellId = input.id || window.location.pathname.split('/').pop(); const requestBody = { id: spellId, version: "live", inputs: input, glifRunIsPublic: !isPrivate }; const response = await fetch('https://glif.app/api/run-glif', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(requestBody) }); if (!response.ok) { throw new Error(`Failed to generate image: ${await response.text()}`); } const reader = response.body.getReader(); let finalImageUrl = null; let nodeOutputs = {}; let graphExecutionState = null; let spellRun = null; let rawResponse = []; let allImageUrls = new Set(); let lastNodeWithImage = null; while (true) { const {done, value} = await reader.read(); if (done) break; const chunk = new TextDecoder().decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const jsonStr = line.slice(6); if (jsonStr.trim()) { try { const data = JSON.parse(jsonStr); rawResponse.push(data); if (data.type === 'image' && data.url) { finalImageUrl = data.url; } if (data.spellRun) { spellRun = data.spellRun; } if (data.graphExecutionState) { graphExecutionState = data.graphExecutionState; Object.entries(data.graphExecutionState.nodes).forEach(([nodeName, nodeData]) => { if (nodeData.output) { nodeOutputs[nodeName] = nodeData.output; if (nodeData.output.type === 'IMAGE') { allImageUrls.add(nodeData.output.value); lastNodeWithImage = nodeData.output.value; const isOutputNode = !Object.values(data.graphExecutionState.nodes).some(node => node.inputs && Object.values(node.inputs).some(input => input.connectionId && input.connectionId.startsWith(nodeName) ) ); if (isOutputNode) { finalImageUrl = nodeData.output.value; } } } }); } } catch (e) { console.error('Error parsing stream data:', e); } } } } } const displayImageUrl = finalImageUrl || lastNodeWithImage || Array.from(allImageUrls).pop(); if (!displayImageUrl) { throw new Error('No image generated'); } // Only show popup for private runs from the original form if (isPrivate && input.isFromForm) { showImagePopup(displayImageUrl); } // Get prompt from inputs const prompt = Object.values(input)[0] || 'No prompt available'; const historyEntry = { url: displayImageUrl, timestamp: new Date().toISOString(), isPrivate: isPrivate, prompt: prompt, spellId: spellRun?.spellId, spellName: spellRun?.spell?.name, runId: spellRun?.id, inputs: input, user: spellRun?.user, clientType: spellRun?.clientType, nodeOutputs, graphExecutionState, rawResponse: rawResponse.length > 0 ? rawResponse : undefined, allImageUrls: Array.from(allImageUrls) }; saveToHistory(historyEntry); return { imageUrl: displayImageUrl, isPrivate }; } // Display batch results function displayBatchResults(results) { const batchPanel = document.querySelector('.batch-overlay'); if (!batchPanel) return; const content = batchPanel.querySelector('.batch-content'); if (!content) return; content.innerHTML = ''; const container = document.createElement('div'); container.className = 'batch-results-container'; const header = document.createElement('div'); header.className = 'batch-results-header'; header.innerHTML = ` <div class="batch-results-title"> ${icons.batch}<span>Batch Results</span> </div> <div class="batch-results-stats"> <span class="success-count">${results.filter(r => r.status === 'success').length} Successful</span> <span class="separator">•</span> <span class="failed-count">${results.filter(r => r.status === 'error').length} Failed</span> </div> `; container.appendChild(header); const grid = document.createElement('div'); grid.className = 'batch-results-grid'; results.forEach(result => { const item = document.createElement('div'); item.className = `batch-result-item ${result.status}`; // Extract prompt from inputs object let prompt = 'No prompt available'; if (result.inputs && typeof result.inputs === 'object') { const firstValue = Object.values(result.inputs)[0]; if (firstValue) { prompt = firstValue; } } if (result.status === 'success') { item.innerHTML = ` <div class="result-image-wrapper"> <img src="${result.finalOutput}" class="result-image" onclick="window.open('${result.finalOutput}', '_blank')"> </div> <div class="result-details"> <div class="result-prompt">${prompt}</div> <div class="result-metadata"> <span class="result-timestamp">${new Date().toLocaleString()}</span> <span class="result-privacy">${result.isPrivate ? 'Private' : 'Public'}</span> </div> </div> `; } else { item.innerHTML = ` <div class="error-container"> ${icons.error} <div class="error-message">${result.error}</div> </div> <div class="result-details"> <div class="result-prompt">${prompt}</div> </div> `; } grid.appendChild(item); }); container.appendChild(grid); const actions = document.createElement('div'); actions.className = 'batch-actions'; actions.innerHTML = ` <button class="batch-action-button new-batch">Start New Batch</button> `; const newBatchBtn = actions.querySelector('.new-batch'); newBatchBtn.addEventListener('click', () => displayBatchPanel()); container.appendChild(actions); content.appendChild(container); // Update styles const style = document.createElement('style'); style.textContent = ` .batch-results-container { padding: 2rem; background: white; border-radius: 12px; max-width: 1200px; margin: 0 auto; } .batch-results-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; padding-bottom: 1rem; border-bottom: 1px solid #e5e7eb; } .batch-results-title { display: flex; align-items: center; gap: 0.75rem; font-size: 1.25rem; font-weight: 600; color: #1f2937; } .batch-results-stats { display: flex; align-items: center; gap: 0.75rem; font-size: 0.875rem; } .success-count { color: #059669; } .failed-count { color: #dc2626; } .separator { color: #d1d5db; } .batch-results-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1.5rem; margin: 0; } .batch-result-item { background: white; border-radius: 16px; overflow: hidden; transition: all 0.3s ease; cursor: pointer; border: 1px solid #e5e7eb; display: flex; flex-direction: column; } .batch-result-item:hover { transform: translateY(-4px); box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1); border-color: var(--accent-color); } .batch-result-item.success { border-color: var(--success-color); } .batch-result-item.error { border-color: var(--error-color); } .result-image-wrapper { position: relative; padding-top: 100%; background: #f3f4f6; overflow: hidden; } .result-image { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; cursor: pointer; } .result-image:hover { transform: scale(1.05); } .result-details { padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem; } .result-prompt { font-size: 0.875rem; color: #1f2937; font-weight: 500; line-height: 1.25rem; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; } .result-prompt:hover { -webkit-line-clamp: unset; } .result-metadata { display: flex; align-items: center; gap: 0.75rem; font-size: 0.75rem; color: #6b7280; } .error-container { padding: 2rem; text-align: center; background: #fef2f2; color: #dc2626; min-height: 200px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.75rem; } .error-message { font-size: 0.875rem; line-height: 1.4; } .batch-actions { display: flex; justify-content: center; gap: 1rem; margin-top: 2rem; } .batch-action-button { padding: 0.75rem 1.5rem; border-radius: 6px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; border: 1px solid var(--border-color); background: var(--background); color: var(--foreground); } .batch-action-button.new-batch { background: #6366f1; color: white; } .batch-action-button.new-batch:hover { background: #4f46e5; } `; document.head.appendChild(style); } // Display batch panel function displayBatchPanel() { const existingPanel = document.querySelector('.batch-overlay'); if (existingPanel) { existingPanel.remove(); } const overlay = document.createElement('div'); overlay.className = 'batch-overlay'; overlay.addEventListener('click', (e) => { if (e.target === overlay) { overlay.remove(); } }); const panel = document.createElement('div'); panel.className = 'batch-panel'; const header = document.createElement('div'); header.className = 'batch-header'; const titleSection = document.createElement('div'); titleSection.className = 'batch-title-section'; const title = document.createElement('h2'); title.className = 'batch-title'; title.innerHTML = `${icons.batch}<span>Batch Generator</span>`; const closeButton = document.createElement('button'); closeButton.className = 'batch-close-button'; closeButton.innerHTML = icons.close; closeButton.addEventListener('click', () => overlay.remove()); titleSection.appendChild(title); titleSection.appendChild(closeButton); const controls = document.createElement('div'); controls.className = 'batch-controls'; const addButton = document.createElement('button'); addButton.className = 'batch-control-button'; addButton.innerHTML = `${icons.add}<span>Add Row</span>`; const toggleButton = document.createElement('button'); toggleButton.id = 'batchPrivateToggle'; toggleButton.className = 'batch-control-button'; const updateToggleState = (isPrivate) => { toggleButton.innerHTML = isPrivate ? `${icons.lock}<span>Private</span>` : `${icons.globe}<span>Public</span>`; toggleButton.classList.toggle('private', isPrivate); }; const initialState = GM_getValue('isPrivate', true); updateToggleState(initialState); toggleButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const newState = !GM_getValue('isPrivate', false); GM_setValue('isPrivate', newState); updateToggleState(newState); }); controls.appendChild(addButton); controls.appendChild(toggleButton); header.appendChild(titleSection); header.appendChild(controls); const content = document.createElement('div'); content.className = 'batch-content'; const inputContainer = document.createElement('div'); inputContainer.id = 'batchInputContainer'; inputContainer.className = 'batch-input-container'; // Create input fields function createInputRow(values = null) { const row = document.createElement('div'); row.className = 'batch-input-row'; const inputsContainer = document.createElement('div'); inputsContainer.className = 'batch-input-fields'; const workflowInputs = getWorkflowInputs(); workflowInputs.forEach(input => { const inputField = document.createElement('input'); inputField.type = input.type; inputField.name = input.name; inputField.placeholder = input.label || input.name; inputField.className = 'batch-input'; if (values && values[input.name]) { inputField.value = values[input.name]; } inputsContainer.appendChild(inputField); }); const actionButtons = document.createElement('div'); actionButtons.className = 'batch-row-actions'; const duplicateButton = document.createElement('button'); duplicateButton.className = 'batch-row-action-button'; duplicateButton.innerHTML = `${icons.copy}<span class="sr-only">Duplicate Row</span>`; duplicateButton.title = 'Duplicate Row'; duplicateButton.addEventListener('click', () => { const values = {}; row.querySelectorAll('input').forEach(input => { if (input.name) { values[input.name] = input.value; } }); const newRow = createInputRow(values); row.parentNode.insertBefore(newRow, row.nextSibling); }); const removeButton = document.createElement('button'); removeButton.className = 'batch-row-action-button delete'; removeButton.innerHTML = `${icons.trash}<span class="sr-only">Remove Row</span>`; removeButton.title = 'Remove Row'; removeButton.addEventListener('click', () => row.remove()); actionButtons.appendChild(duplicateButton); actionButtons.appendChild(removeButton); row.appendChild(inputsContainer); row.appendChild(actionButtons); return row; } addButton.addEventListener('click', () => { inputContainer.appendChild(createInputRow()); }); const generateButton = document.createElement('button'); generateButton.className = 'batch-generate-button'; generateButton.innerHTML = `${icons.generate}<span>Generate</span>`; generateButton.addEventListener('click', async () => { const inputs = []; inputContainer.querySelectorAll('.batch-input-row').forEach(row => { const rowInputs = {}; row.querySelectorAll('input').forEach(input => { if (input.name && input.value) { rowInputs[input.name] = input.value; } }); if (Object.keys(rowInputs).length > 0) { inputs.push(rowInputs); } }); if (inputs.length === 0) { alert('Please add at least one input'); return; } // Only remove the add row and public/private buttons from controls addButton.remove(); toggleButton.remove(); inputContainer.remove(); buttonContainer.remove(); // Clear existing content content.innerHTML = ''; // Create progress container with modern styling const progressContainer = document.createElement('div'); progressContainer.className = 'batch-progress-container'; progressContainer.innerHTML = ` <div class="progress-header"> <h3>Generating Images</h3> <span class="progress-count">0/${inputs.length}</span> </div> <div class="progress-bar"> <div class="progress-fill"></div> </div> <div class="progress-details"> <span class="progress-percentage">0%</span> <span class="progress-message">Starting batch generation...</span> </div> `; content.appendChild(progressContainer); try { const results = await processBatchGeneration(inputs, GM_getValue('isPrivate', true)); displayBatchResults(results); } catch (error) { console.error('Error processing batch:', error); content.innerHTML = ` <div class="batch-error"> ${icons.error}<span>Error processing batch: ${error.message}</span> </div> `; } }); const buttonContainer = document.createElement('div'); buttonContainer.className = 'batch-button-container'; buttonContainer.appendChild(generateButton); content.appendChild(inputContainer); content.appendChild(buttonContainer); // Add initial input row inputContainer.appendChild(createInputRow()); panel.appendChild(header); panel.appendChild(content); overlay.appendChild(panel); document.body.appendChild(overlay); } // Process batch generation async function processBatchGeneration(inputs, isPrivate = true) { const results = []; let completed = 0; // Create array of promises for parallel execution const promises = inputs.map(async (input, index) => { try { const result = await generateImage(input); completed++; // Update progress UI const progress = (completed / inputs.length) * 100; const progressFill = document.querySelector('.progress-fill'); const progressCount = document.querySelector('.progress-count'); const progressPercentage = document.querySelector('.progress-percentage'); const progressMessage = document.querySelector('.progress-message'); progressFill.style.width = `${progress}%`; progressCount.textContent = `${completed}/${inputs.length}`; progressPercentage.textContent = `${Math.round(progress)}%`; progressMessage.textContent = `Generated ${completed} of ${inputs.length} images...`; return { status: 'success', finalOutput: result.imageUrl, inputs: input, isPrivate: result.isPrivate }; } catch (error) { completed++; // Update progress UI const progress = (completed / inputs.length) * 100; const progressFill = document.querySelector('.progress-fill'); const progressCount = document.querySelector('.progress-count'); const progressPercentage = document.querySelector('.progress-percentage'); const progressMessage = document.querySelector('.progress-message'); progressFill.style.width = `${progress}%`; progressCount.textContent = `${completed}/${inputs.length}`; progressPercentage.textContent = `${Math.round(progress)}%`; progressMessage.textContent = `Generated ${completed} of ${inputs.length} images...`; return { status: 'error', error: error.message, inputs: input }; } }); // Wait for all promises to resolve const batchResults = await Promise.all(promises); results.push(...batchResults); // Update final progress const progressMessage = document.querySelector('.progress-message'); progressMessage.textContent = 'Generation complete!'; // Short delay before showing results await new Promise(resolve => setTimeout(resolve, 500)); return results; } // Create and show final image popup function showImagePopup(imageUrl) { // Remove existing popup if any const existingPopup = document.querySelector('.final-image-popup'); if (existingPopup) { existingPopup.remove(); } const popup = document.createElement('div'); popup.className = 'final-image-popup'; popup.innerHTML = ` <div class="popup-content"> <img src="${imageUrl}" alt="Generated Image" /> <button class="open-new-tab" onclick="window.open('${imageUrl}', '_blank')"> Open in New Tab </button> </div> `; document.body.appendChild(popup); // Auto-remove after 10 seconds setTimeout(() => { popup.remove(); }, 10000); } // Show toast notification function showToast(message, type = 'success') { // Remove any existing toasts const existingToast = document.querySelector('.glif-toast'); if (existingToast) { existingToast.remove(); } const toast = document.createElement('div'); toast.className = `glif-toast ${type}`; const icon = type === 'success' ? `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5"></path></svg>` : `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>`; toast.innerHTML = `${icon}<span>${message}</span>`; document.body.appendChild(toast); // Remove toast after 3 seconds setTimeout(() => { toast.style.animation = 'fadeOut 0.3s ease-out forwards'; setTimeout(() => toast.remove(), 300); }, 3000); } // Add private toggle function addPrivateToggle() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'childList') { const runButton = Array.from(document.querySelectorAll('button')).find( button => button.textContent.includes('Run This Glif') ); if (runButton && !document.getElementById('privateToggle')) { const toggle = document.createElement('button'); toggle.id = 'privateToggle'; toggle.className = runButton.className; toggle.style.cssText = ` margin-top: 8px; transition: all 0.2s ease; font-weight: 500; width: 100%; `; const updateButtonState = (isPrivate) => { toggle.innerHTML = isPrivate ? `${icons.lock}<span>Private</span>` : `${icons.globe}<span>Public</span>`; toggle.style.backgroundColor = isPrivate ? '#dc2626' : '#000000'; toggle.style.color = '#ffffff'; }; const initialState = GM_getValue('isPrivate', true); updateButtonState(initialState); toggle.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const newState = !GM_getValue('isPrivate', false); GM_setValue('isPrivate', newState); updateButtonState(newState); }); runButton.parentNode.appendChild(toggle); } } }); }); observer.observe(document.body, { childList: true, subtree: true }); } // Initialize function initialize() { // Inject styles first injectStyles(); // Set initial dark mode state document.documentElement.setAttribute('data-glif-dark-mode', isDarkMode); // Function to initialize tools const initializeTools = () => { const navbar = document.querySelector('.flex.gap-3.md\\:gap-\\[44px\\]'); if (navbar && !document.querySelector('.glif-tools-dropdown')) { const toolsDropdown = createToolsDropdown(); navbar.appendChild(toolsDropdown); addPrivateToggle(); return true; } return false; }; // Initial attempt if (!initializeTools()) { // Set up mutation observer for dynamic content const observer = new MutationObserver((mutations, obs) => { if (document.querySelector('.flex.gap-3.md\\:gap-\\[44px\\]') && !document.querySelector('.glif-tools-dropdown')) { if (initializeTools()) { obs.disconnect(); } } }); // Start observing with more comprehensive options observer.observe(document.body, { childList: true, subtree: true, attributes: true }); // Backup timeout attempts const attempts = [500, 1000, 2000, 3000]; attempts.forEach(timeout => { setTimeout(() => { if (!document.querySelector('.glif-tools-dropdown')) { initializeTools(); } }, timeout); }); } } // Initial setup initialize(); })(); const batchStyles = ` .batch-progress-container { padding: 2rem; background: var(--background, white); border-radius: 12px; max-width: 1200px; margin: 0 auto; border: 1px solid var(--border-color, #e5e7eb); } .progress-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; padding-bottom: 1rem; border-bottom: 1px solid var(--border-color, #e5e7eb); } .progress-header h3 { font-size: 1.25rem; font-weight: 600; color: var(--foreground, #1f2937); margin: 0; } .progress-count { font-size: 0.875rem; color: var(--foreground-secondary, #6b7280); font-weight: 500; } .progress-bar { width: 100%; height: 8px; background: var(--background-secondary, #f3f4f6); border-radius: 999px; overflow: hidden; margin: 1rem 0; } .progress-fill { height: 100%; background: var(--accent-color, #6366f1); border-radius: 999px; transition: width 0.3s ease; width: 0%; } .progress-details { display: flex; justify-content: space-between; align-items: center; margin-top: 0.75rem; } .progress-percentage { font-size: 0.875rem; font-weight: 500; color: var(--accent-color, #6366f1); } .progress-message { font-size: 0.875rem; color: var(--foreground-secondary, #6b7280); } /* Dark mode styles for progress and results */ [data-glif-dark-mode="true"] .batch-progress-container { background-color: #1E1E1E !important; border-color: #2E2E2E !important; } [data-glif-dark-mode="true"] .progress-header { border-bottom-color: #2E2E2E !important; } [data-glif-dark-mode="true"] .progress-header h3 { color: #FFFFFF !important; } [data-glif-dark-mode="true"] .progress-count { color: #9CA3AF !important; } [data-glif-dark-mode="true"] .progress-bar { background-color: #2A2A2A !important; } [data-glif-dark-mode="true"] .progress-fill { background-color: #6366F1 !important; } [data-glif-dark-mode="true"] .progress-percentage { color: #818CF8 !important; } [data-glif-dark-mode="true"] .progress-message { color: #9CA3AF !important; } [data-glif-dark-mode="true"] .batch-results-container { background-color: #1E1E1E !important; border-color: #2E2E2E !important; } [data-glif-dark-mode="true"] .batch-results-header { border-bottom-color: #2E2E2E !important; } [data-glif-dark-mode="true"] .batch-results-title { color: #FFFFFF !important; } [data-glif-dark-mode="true"] .batch-result-item { background-color: #25262B !important; border-color: #2E2E2E !important; } [data-glif-dark-mode="true"] .batch-result-item:hover { background-color: #2C2D32 !important; border-color: #6366F1 !important; } [data-glif-dark-mode="true"] .result-prompt { color: #E6E8EC !important; } [data-glif-dark-mode="true"] .result-metadata { color: #9CA3AF !important; } [data-glif-dark-mode="true"] .error-container { background-color: rgba(239, 68, 68, 0.1) !important; border-color: rgba(239, 68, 68, 0.2) !important; } [data-glif-dark-mode="true"] .error-message { color: #EF4444 !important; } [data-glif-dark-mode="true"] .batch-action-button { background-color: #2A2A2A !important; color: #FFFFFF !important; border-color: #3E3E3E !important; } [data-glif-dark-mode="true"] .batch-action-button:hover { background-color: #33363C !important; border-color: #6366F1 !important; } .batch-error { display: flex; align-items: center; gap: 0.5rem; padding: 1rem; background-color: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.2); border-radius: 8px; color: #EF4444; margin: 1rem 0; } `; // Add styles to document head const styleElement = document.createElement('style'); styleElement.textContent = batchStyles; document.head.appendChild(styleElement); generateButton.addEventListener('click', async () => { const inputs = []; inputContainer.querySelectorAll('.batch-input-row').forEach(row => { const rowInputs = {}; row.querySelectorAll('input').forEach(input => { if (input.name && input.value) { rowInputs[input.name] = input.value; } }); if (Object.keys(rowInputs).length > 0) { inputs.push(rowInputs); } }); if (inputs.length === 0) { alert('Please add at least one input row with values'); return; } // Only remove the add row and public/private buttons from controls addButton.remove(); toggleButton.remove(); inputContainer.remove(); buttonContainer.remove(); // Clear existing content content.innerHTML = ''; // Create progress container with modern styling const progressContainer = document.createElement('div'); progressContainer.className = 'batch-progress-container'; progressContainer.innerHTML = ` <div class="progress-header"> <h3>Generating Images</h3> <span class="progress-count">0/${inputs.length}</span> </div> <div class="progress-bar"> <div class="progress-fill"></div> </div> <div class="progress-details"> <span class="progress-percentage">0%</span> <span class="progress-message">Starting batch generation...</span> </div> `; content.appendChild(progressContainer); try { const results = await processBatchGeneration(inputs, GM_getValue('isPrivate', true)); displayBatchResults(results); } catch (error) { console.error('Error processing batch:', error); content.innerHTML = ` <div class="batch-error"> ${icons.error}<span>Error processing batch: ${error.message}</span> </div> `; } });