GLIF Tools

A set of usefull tools for GLIF.APP

As of 2024-12-02. See the latest version.

// ==UserScript==
// @name         GLIF Tools
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  A set of usefull tools for GLIF.APP
// @author       i12bp8
// @match        https://glif.app/*
// @run-at       document-start
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        unsafeWindow
// ==/UserScript==

(function() {
    'use strict';
    const originalFetch = unsafeWindow.fetch;

    // Replace the global 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', true);

            // Set private/public mode
            body.glifRunIsPublic = !isPrivate;
            modifiedOptions.body = JSON.stringify(body);

            const response = await originalFetch(url, modifiedOptions);
            const clonedResponse = response.clone();

            // Process the response stream
            processStreamResponse(clonedResponse, isPrivate, body.inputs).catch(err => {
                console.error('Error processing response:', err);
            });

            return response;
        }

        return originalFetch(...args);
    };

    function saveToHistory(entry) {
        const history = GM_getValue('imageHistory', []);

        // Check if this image is already in history to prevent duplicates
        const isDuplicate = history.some(item =>
            item.url === entry.url &&
            item.timestamp === entry.timestamp
        );

        if (!isDuplicate) {
            // Add new entry to the beginning of the array
            history.unshift(entry);

            // Keep only the last 100 entries to manage storage
            if (history.length > 100) {
                history.pop();
            }

            // Save updated history
            GM_setValue('imageHistory', history);
            console.log('Saved to history:', entry);
        }
    }

    function displayHistoryPanel() {
        let historyPanel = document.getElementById('historyPanel');
        if (historyPanel) {
            historyPanel.remove();
            return;
        }

        const history = GM_getValue('imageHistory', []);

        historyPanel = document.createElement('div');
        historyPanel.id = 'historyPanel';
        historyPanel.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            padding: 24px;
            border-radius: 16px;
            box-shadow: 0 8px 32px rgba(0,0,0,0.15);
            z-index: 10001;
            max-height: 85vh;
            width: 85vw;
            max-width: 1200px;
            overflow-y: auto;
            display: flex;
            flex-direction: column;
            gap: 16px;
        `;

        // Create header
        const header = document.createElement('div');
        header.style.cssText = `
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding-bottom: 15px;
            border-bottom: 1px solid #eee;
            gap: 16px;
        `;

        const titleSection = document.createElement('div');
        titleSection.style.cssText = `
            display: flex;
            align-items: center;
            gap: 12px;
        `;

        const title = document.createElement('h2');
        title.textContent = 'Image History';
        title.style.cssText = `
            margin: 0;
            font-size: 24px;
            font-weight: 600;
            color: #1a1a1a;
        `;

        const filterContainer = document.createElement('div');
        filterContainer.style.cssText = `
            display: flex;
            align-items: center;
            gap: 8px;
            background: #f5f5f5;
            padding: 4px;
            border-radius: 8px;
        `;

        const createFilterButton = (text, filter) => {
            const button = document.createElement('button');
            button.textContent = text;
            button.style.cssText = `
                padding: 6px 12px;
                border-radius: 6px;
                border: none;
                cursor: pointer;
                font-size: 14px;
                transition: all 0.2s ease;
            `;

            const updateFilter = (selectedFilter) => {
                const items = grid.children;
                Array.from(items).forEach(item => {
                    const isPrivate = item.dataset.private === 'true';
                    item.style.display =
                        selectedFilter === 'all' ||
                        (selectedFilter === 'private' && isPrivate) ||
                        (selectedFilter === 'public' && !isPrivate)
                            ? 'block'
                            : 'none';
                });

                filterContainer.querySelectorAll('button').forEach(btn => {
                    btn.style.background = 'transparent';
                    btn.style.color = '#666';
                });
                button.style.background = selectedFilter === filter ? 'white' : 'transparent';
                button.style.color = selectedFilter === filter ? '#1a1a1a' : '#666';
            };

            button.onclick = () => updateFilter(filter);
            if (filter === 'all') {
                button.style.background = 'white';
                button.style.color = '#1a1a1a';
            }
            return button;
        };

        filterContainer.appendChild(createFilterButton('All', 'all'));
        filterContainer.appendChild(createFilterButton('Private', 'private'));
        filterContainer.appendChild(createFilterButton('Public', 'public'));

        const actionsContainer = document.createElement('div');
        actionsContainer.style.cssText = `
            display: flex;
            align-items: center;
            gap: 12px;
        `;

        const clearButton = document.createElement('button');
        clearButton.innerHTML = '🗑️ Clear History';
        clearButton.style.cssText = `
            border: none;
            background: #fee2e2;
            color: #dc2626;
            padding: 8px 16px;
            border-radius: 8px;
            cursor: pointer;
            font-weight: 500;
            transition: all 0.2s ease;
        `;
        clearButton.onmouseover = () => clearButton.style.background = '#fecaca';
        clearButton.onmouseout = () => clearButton.style.background = '#fee2e2';
        clearButton.onclick = () => {
            if (confirm('Are you sure you want to clear the history?')) {
                GM_setValue('imageHistory', []);
                historyPanel.remove();
            }
        };

        const closeButton = document.createElement('button');
        closeButton.innerHTML = '✕';
        closeButton.style.cssText = `
            border: none;
            background: none;
            cursor: pointer;
            font-size: 20px;
            padding: 8px;
            color: #666;
        `;
        closeButton.onmouseover = () => closeButton.style.color = '#1a1a1a';
        closeButton.onmouseout = () => closeButton.style.color = '#666';
        closeButton.onclick = () => historyPanel.remove();

        // Assemble header
        titleSection.appendChild(title);
        titleSection.appendChild(filterContainer);
        header.appendChild(titleSection);
        actionsContainer.appendChild(clearButton);
        actionsContainer.appendChild(closeButton);
        header.appendChild(actionsContainer);

        // Create grid
        const grid = document.createElement('div');
        grid.style.cssText = `
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
            gap: 20px;
            padding: 4px;
        `;

        // Populate grid with history items
        history.forEach(entry => {
            const item = document.createElement('div');
            item.dataset.private = entry.isPrivate;
            item.style.cssText = `
                border: 1px solid #e5e7eb;
                border-radius: 16px;
                overflow: hidden;
                transition: all 0.2s ease;
                background: white;
            `;

            const img = document.createElement('img');
            img.src = entry.url;
            img.style.cssText = `
                width: 100%;
                height: 300px;
                object-fit: cover;
                cursor: pointer;
            `;
            img.onclick = () => window.open(entry.url, '_blank');

            const info = document.createElement('div');
            info.style.cssText = `
                padding: 16px;
                display: flex;
                flex-direction: column;
                gap: 12px;
            `;

            const header = document.createElement('div');
            header.style.cssText = `
                display: flex;
                justify-content: space-between;
                align-items: center;
            `;

            const date = document.createElement('span');
            date.textContent = new Date(entry.timestamp).toLocaleString();
            date.style.cssText = `
                color: #6b7280;
                font-size: 13px;
            `;

            const privacyBadge = document.createElement('span');
            privacyBadge.textContent = entry.isPrivate ? '🔒' : '🌐';
            privacyBadge.title = entry.isPrivate ? 'Private' : 'Public';
            privacyBadge.style.cssText = `
                font-size: 16px;
            `;

            header.appendChild(date);
            header.appendChild(privacyBadge);

            const details = document.createElement('div');
            details.style.cssText = `
                display: flex;
                flex-direction: column;
                gap: 8px;
            `;

            // Add GLIF name with link
            if (entry.spellName && entry.spellId) {
                const glifLink = document.createElement('a');
                const runUrl = entry.runId ?
                    `https://glif.app/@${entry.user?.name}/runs/${entry.runId}` :
                    `https://glif.app/spell/${entry.spellId}`;
                glifLink.href = runUrl;
                glifLink.target = '_blank';
                glifLink.innerHTML = `<b>GLIF:</b> ${entry.spellName}`;
                glifLink.style.cssText = `
                    color: #2563eb;
                    text-decoration: none;
                    font-size: 14px;
                    &:hover {
                        text-decoration: underline;
                    }
                `;
                details.appendChild(glifLink);
            }

            // Add input variables
            if (entry.inputs && Object.keys(entry.inputs).length > 0) {
                const inputsContainer = document.createElement('div');
                inputsContainer.className = 'input-values';
                inputsContainer.style.cssText = `
                    display: flex;
                    flex-direction: column;
                    gap: 4px;
                    background: #f9fafb;
                    padding: 8px 12px;
                    border-radius: 8px;
                    font-size: 13px;
                `;

                Object.entries(entry.inputs).forEach(([key, value]) => {
                    const inputRow = document.createElement('div');
                    inputRow.style.cssText = `
                        display: flex;
                        gap: 8px;
                        align-items: baseline;
                    `;
                    const keySpan = document.createElement('span');
                    keySpan.textContent = key + ':';
                    keySpan.style.cssText = `
                        font-weight: 600;
                        color: #4b5563;
                        flex-shrink: 0;
                    `;
                    const valueSpan = document.createElement('span');
                    valueSpan.textContent = value;
                    valueSpan.style.cssText = `
                        color: #6b7280;
                        word-break: break-word;
                    `;
                    inputRow.appendChild(keySpan);
                    inputRow.appendChild(valueSpan);
                    inputsContainer.appendChild(inputRow);
                });

                details.appendChild(inputsContainer);
            }

            // Add view data button
            const viewDataButton = document.createElement('button');
            viewDataButton.textContent = '🔍 View Details';
            viewDataButton.style.cssText = `
                padding: 8px 16px;
                background: #f3f4f6;
                border: none;
                border-radius: 6px;
                cursor: pointer;
                font-size: 14px;
                transition: all 0.2s ease;
                margin-top: 8px;
                &:hover {
                    background: #e5e7eb;
                }
            `;
            viewDataButton.onclick = () => displayGlifData(entry);
            details.appendChild(viewDataButton);

            info.appendChild(header);
            info.appendChild(details);
            item.appendChild(img);
            item.appendChild(info);

            grid.appendChild(item);
        });

        historyPanel.appendChild(header);
        historyPanel.appendChild(grid);
        document.body.appendChild(historyPanel);
    }

    function displayGlifData(entry) {
        const modal = document.createElement('div');
        modal.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.5);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 100000;
        `;

        const content = document.createElement('div');
        content.style.cssText = `
            background: white;
            padding: 24px;
            border-radius: 12px;
            max-width: 90%;
            max-height: 90vh;
            overflow-y: auto;
            position: relative;
            width: 800px;
            z-index: 100001;
        `;

        // Close button
        const closeBtn = document.createElement('button');
        closeBtn.innerHTML = '✕';
        closeBtn.style.cssText = `
            position: absolute;
            top: 16px;
            right: 16px;
            border: none;
            background: none;
            font-size: 20px;
            cursor: pointer;
            color: #666;
        `;
        closeBtn.onclick = () => modal.remove();
        content.appendChild(closeBtn);

        // Title
        const title = document.createElement('h2');
        title.textContent = entry.spellName || 'GLIF Run Details';
        title.style.cssText = `
            margin: 0 0 20px 0;
            font-size: 24px;
            color: #111827;
        `;
        content.appendChild(title);

        // Main image preview
        const imagePreview = document.createElement('div');
        imagePreview.style.cssText = `
            margin-bottom: 24px;
            text-align: center;
        `;
        const mainImage = document.createElement('img');
        mainImage.src = entry.url;
        mainImage.style.cssText = `
            max-width: 100%;
            max-height: 400px;
            border-radius: 8px;
            cursor: pointer;
        `;
        mainImage.onclick = () => window.open(entry.url, '_blank');
        imagePreview.appendChild(mainImage);
        content.appendChild(imagePreview);

        // Basic Info Section
        const basicInfo = document.createElement('div');
        basicInfo.style.cssText = 'margin-bottom: 24px;';

        const infoTable = document.createElement('table');
        infoTable.style.cssText = `
            width: 100%;
            border-collapse: collapse;
            margin-bottom: 16px;
        `;

        const infoData = {
            'Spell ID': entry.spellId || 'N/A',
            'Run ID': entry.runId || 'N/A',
            'Run Link': entry.runId && entry.user?.name ?
                `https://glif.app/@${entry.user.name}/runs/${entry.runId}` : 'N/A',
            'Timestamp': new Date(entry.timestamp).toLocaleString(),
            'Private': entry.isPrivate ? '🔒 Yes' : '🌐 No',
            'Client Type': entry.clientType || 'N/A',
            'User': entry.user?.name || 'N/A'
        };

        Object.entries(infoData).forEach(([key, value]) => {
            const row = infoTable.insertRow();
            const keyCell = row.insertCell();
            const valueCell = row.insertCell();

            keyCell.style.cssText = `
                padding: 8px;
                font-weight: 500;
                color: #4b5563;
                width: 120px;
                background: #f9fafb;
            `;
            valueCell.style.cssText = `
                padding: 8px;
                color: #111827;
            `;

            keyCell.textContent = key;
            valueCell.textContent = value;
        });

        basicInfo.appendChild(infoTable);
        content.appendChild(basicInfo);

        // Node Outputs Section
        if (entry.nodeOutputs && Object.keys(entry.nodeOutputs).length > 0) {
            const nodeOutputsTitle = document.createElement('h3');
            nodeOutputsTitle.textContent = 'Node Outputs';
            nodeOutputsTitle.style.cssText = `
                margin: 24px 0 16px 0;
                font-size: 18px;
                color: #111827;
            `;
            content.appendChild(nodeOutputsTitle);
            content.appendChild(createNodeOutputsSection(entry.nodeOutputs));
        }

        // All Generated Images Section
        if (entry.allImageUrls && entry.allImageUrls.length > 0) {
            const imagesTitle = document.createElement('h3');
            imagesTitle.textContent = 'All Generated Images';
            imagesTitle.style.cssText = `
                margin: 24px 0 16px 0;
                font-size: 18px;
                color: #111827;
            `;
            content.appendChild(imagesTitle);

            const imagesGrid = document.createElement('div');
            imagesGrid.style.cssText = `
                display: grid;
                grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
                gap: 16px;
                margin-bottom: 24px;
            `;

            entry.allImageUrls.forEach(url => {
                const imgContainer = document.createElement('div');
                imgContainer.style.cssText = `
                    background: #f9fafb;
                    padding: 8px;
                    border-radius: 8px;
                    text-align: center;
                `;

                const img = document.createElement('img');
                img.src = url;
                img.style.cssText = `
                    max-width: 100%;
                    height: auto;
                    border-radius: 4px;
                    cursor: pointer;
                `;
                img.onclick = () => window.open(url, '_blank');

                imgContainer.appendChild(img);
                imagesGrid.appendChild(imgContainer);
            });

            content.appendChild(imagesGrid);
        }

        // Raw Data section with collapsible panels
        if (entry.graphExecutionState || entry.rawResponse) {
            const rawDataTitle = document.createElement('h3');
            rawDataTitle.textContent = 'Technical Details';
            rawDataTitle.style.cssText = `
                margin: 24px 0 16px 0;
                font-size: 18px;
                color: #111827;
            `;
            content.appendChild(rawDataTitle);

            if (entry.graphExecutionState) {
                const graphStateContent = formatJSONDisplay(entry.graphExecutionState);
                content.appendChild(createCollapsibleSection('Graph Execution State', graphStateContent));
            }

            if (entry.rawResponse) {
                const rawResponseContent = formatJSONDisplay(entry.rawResponse);
                content.appendChild(createCollapsibleSection('Raw Response Data', rawResponseContent));
            }
        }

        // Input Parameters section if available
        if (entry.inputs && Object.keys(entry.inputs).length > 0) {
            const inputsTitle = document.createElement('h3');
            inputsTitle.textContent = 'Input Parameters';
            inputsTitle.style.cssText = `
                margin: 24px 0 16px 0;
                font-size: 18px;
                color: #111827;
            `;
            content.appendChild(inputsTitle);

            const inputsContent = formatJSONDisplay(entry.inputs);
            content.appendChild(createCollapsibleSection('Parameters', inputsContent));
        }

        modal.appendChild(content);
        document.body.appendChild(modal);
    }

    function formatJSONDisplay(jsonData) {
        const container = document.createElement('div');
        container.style.cssText = `
            background: #f8fafc;
            padding: 16px;
            border-radius: 8px;
            font-family: monospace;
            white-space: pre-wrap;
            font-size: 13px;
            color: #334155;
            overflow-x: auto;
        `;

        // Replace escaped newlines with actual newlines
        const formattedStr = JSON.stringify(jsonData, null, 2)
            .replace(/\\n/g, '\n')
            .replace(/\\"/g, '"')
            .replace(/\\\\/g, '\\');

        container.textContent = formattedStr;
        return container;
    }

    function createCollapsibleSection(title, content) {
        const section = document.createElement('div');
        section.style.cssText = `
            margin: 16px 0;
            border: 1px solid #e2e8f0;
            border-radius: 8px;
            overflow: hidden;
        `;

        const header = document.createElement('div');
        header.style.cssText = `
            padding: 12px 16px;
            background: #f8fafc;
            cursor: pointer;
            display: flex;
            justify-content: space-between;
            align-items: center;
            user-select: none;
        `;

        const titleSpan = document.createElement('span');
        titleSpan.textContent = title;
        titleSpan.style.cssText = `
            font-weight: 600;
            color: #334155;
        `;

        const arrow = document.createElement('span');
        arrow.textContent = '▼';
        arrow.style.cssText = `
            transition: transform 0.2s;
            color: #64748b;
        `;

        header.appendChild(titleSpan);
        header.appendChild(arrow);

        const contentDiv = document.createElement('div');
        contentDiv.style.cssText = `
            padding: 16px;
            border-top: 1px solid #e2e8f0;
        `;
        contentDiv.appendChild(content);

        let isOpen = true;
        header.onclick = () => {
            isOpen = !isOpen;
            contentDiv.style.display = isOpen ? 'block' : 'none';
            arrow.style.transform = isOpen ? 'rotate(0deg)' : 'rotate(-90deg)';
        };

        section.appendChild(header);
        section.appendChild(contentDiv);
        return section;
    }

    function formatNodeOutput(output) {
        if (!output) return '';

        const container = document.createElement('div');
        container.style.cssText = `
            padding: 8px;
            background: #ffffff;
            border-radius: 4px;
            border: 1px solid #e5e7eb;
        `;

        switch (output.type) {
            case 'IMAGE':
                const img = document.createElement('img');
                img.src = output.value;
                img.style.cssText = `
                    max-width: 100%;
                    height: auto;
                    border-radius: 4px;
                    margin: 4px 0;
                    cursor: pointer;
                `;
                img.onclick = () => window.open(output.value, '_blank');
                container.appendChild(img);
                break;

            case 'TEXT':
                const text = document.createElement('div');
                text.style.cssText = `
                    white-space: pre-wrap;
                    font-family: monospace;
                    font-size: 13px;
                    color: #374151;
                `;
                text.textContent = output.value;
                container.appendChild(text);
                break;

            case 'MULTIPLE':
                Object.entries(output.value).forEach(([key, val]) => {
                    const item = document.createElement('div');
                    item.style.cssText = `
                        margin: 8px 0;
                        padding: 8px;
                        background: #f9fafb;
                        border-radius: 4px;
                    `;

                    const label = document.createElement('div');
                    label.textContent = key;
                    label.style.cssText = `
                        font-weight: 500;
                        color: #4b5563;
                        margin-bottom: 4px;
                    `;

                    item.appendChild(label);
                    item.appendChild(formatNodeOutput(val));
                    container.appendChild(item);
                });
                break;

            default:
                container.textContent = JSON.stringify(output.value, null, 2);
        }

        return container;
    }

    function createNodeOutputsSection(nodeOutputs) {
        const section = document.createElement('div');
        section.style.cssText = `
            display: flex;
            flex-direction: column;
            gap: 16px;
        `;

        Object.entries(nodeOutputs).forEach(([nodeName, output]) => {
            const nodeContainer = document.createElement('div');
            nodeContainer.style.cssText = `
                background: #f9fafb;
                padding: 16px;
                border-radius: 8px;
            `;

            const header = document.createElement('div');
            header.style.cssText = `
                font-weight: 600;
                color: #374151;
                margin-bottom: 12px;
            `;
            header.textContent = nodeName;

            nodeContainer.appendChild(header);
            nodeContainer.appendChild(formatNodeOutput(output));
            section.appendChild(nodeContainer);
        });

        return section;
    }

    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;

        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.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 latestFinalImage = findFinalImageUrl(graphExecutionState);
                                    if (latestFinalImage) {
                                        finalImageUrl = latestFinalImage;
                                    }
                                }
                            } catch (e) {
                                console.error('Error parsing JSON:', e);
                            }
                        }
                    }
                }
            }

            const displayImageUrl = finalImageUrl || lastNodeWithImage || Array.from(allImageUrls).pop();

            if (displayImageUrl) {
                const historyEntry = {
                    url: displayImageUrl,
                    timestamp: new Date().toISOString(),
                    isPrivate: isPrivate,
                    spellId: spellRun?.spellId,
                    spellName: spellRun?.spell?.name,
                    runId: spellRun?.id,
                    inputs: inputs || {},
                    user: spellRun?.user,
                    clientType: spellRun?.clientType,
                    nodeOutputs,
                    graphExecutionState,
                    rawResponse: rawResponse.length > 0 ? rawResponse : undefined,
                    allImageUrls: Array.from(allImageUrls)
                };

                console.log(`Saving history entry for prompt: ${inputs.prompt}`);
                saveToHistory(historyEntry);

                // Show popup for private generations
                if (isPrivate) {
                    showPrivateResultPopup(displayImageUrl);
                }

                return displayImageUrl;
            }
        } catch (error) {
            console.error('Error processing stream:', error);
            throw error;
        }

        return null;
    }

    function showPrivateResultPopup(imageUrl) {
        // Create popup container
        const popup = document.createElement('div');
        popup.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            background: white;
            border-radius: 12px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
            padding: 16px;
            width: 300px;
            z-index: 9999;
            animation: slideIn 0.3s ease-out;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
        `;

        // Add keyframes for animation
        const style = document.createElement('style');
        style.textContent = `
            @keyframes slideIn {
                from {
                    transform: translateX(100%);
                    opacity: 0;
                }
                to {
                    transform: translateX(0);
                    opacity: 1;
                }
            }
        `;
        document.head.appendChild(style);

        // Create header
        const header = document.createElement('div');
        header.style.cssText = `
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 12px;
        `;

        const title = document.createElement('div');
        title.textContent = 'Private Generation Complete';
        title.style.cssText = `
            font-weight: 600;
            color: #1f2937;
            font-size: 14px;
        `;

        const closeButton = document.createElement('button');
        closeButton.innerHTML = '×';
        closeButton.style.cssText = `
            background: none;
            border: none;
            font-size: 20px;
            color: #6b7280;
            cursor: pointer;
            padding: 0;
            width: 24px;
            height: 24px;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 6px;
            transition: background-color 0.2s;
            &:hover {
                background-color: #f3f4f6;
            }
        `;
        closeButton.onclick = () => popup.remove();

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

        // Create image container
        const imageContainer = document.createElement('div');
        imageContainer.style.cssText = `
            position: relative;
            width: 100%;
            border-radius: 8px;
            overflow: hidden;
            margin-bottom: 12px;
            aspect-ratio: 1;
            background: #f3f4f6;
        `;

        const image = document.createElement('img');
        image.src = imageUrl;
        image.style.cssText = `
            width: 100%;
            height: 100%;
            object-fit: cover;
            cursor: pointer;
        `;
        image.onclick = () => window.open(imageUrl, '_blank');

        imageContainer.appendChild(image);

        // Create action button
        const openButton = document.createElement('button');
        openButton.textContent = 'Open in New Tab';
        openButton.style.cssText = `
            width: 100%;
            padding: 8px 16px;
            background: #3b82f6;
            color: white;
            border: none;
            border-radius: 6px;
            font-size: 14px;
            font-weight: 500;
            cursor: pointer;
            transition: background-color 0.2s;
            &:hover {
                background: #2563eb;
            }
        `;
        openButton.onclick = () => window.open(imageUrl, '_blank');

        // Assemble popup
        popup.appendChild(header);
        popup.appendChild(imageContainer);
        popup.appendChild(openButton);

        // Add to document
        document.body.appendChild(popup);

        // Auto-remove after 30 seconds
        setTimeout(() => {
            if (document.body.contains(popup)) {
                popup.style.animation = 'slideIn 0.3s ease-out reverse';
                setTimeout(() => popup.remove(), 300);
            }
        }, 30000);
    }

    async function processBatchGeneration(inputs, isPrivate) {
        const results = [];
        const total = inputs.length;
        let completed = 0;

        // Get the glif ID from the URL
        const glifId = window.location.pathname.split('/').pop();
        if (!glifId) {
            throw new Error('Could not find glif ID');
        }

        // Get the first input field name (usually the prompt field)
        const workflowInputs = getWorkflowInputs();
        const promptFieldName = workflowInputs.length > 0 ? workflowInputs[0].name : null;

        // Create base request data
        const baseRequestData = {
            id: glifId,
            version: "live",
            glifRunIsPublic: !isPrivate
        };

        // Process in parallel with a maximum of 3 concurrent requests
        const batchSize = 3;
        for (let i = 0; i < inputs.length; i += batchSize) {
            const batch = inputs.slice(i, i + batchSize);
            const promises = batch.map(async (input) => {
                try {
                    const requestBody = {
                        ...baseRequestData,
                        inputs: input
                    };

                    console.log('Making request with:', requestBody);

                    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) {
                        const errorText = await response.text();
                        console.error('API Error:', errorText);
                        throw new Error(`Failed to generate image: ${errorText}`);
                    }

                    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.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 = {
                                                            url: nodeData.output.value,
                                                            node: nodeName
                                                        };
                                                    }
                                                }
                                            });

                                            const latestFinalImage = findFinalImageUrl(graphExecutionState);
                                            if (latestFinalImage) {
                                                finalImageUrl = latestFinalImage;
                                            }
                                        }
                                    } catch (e) {
                                        console.error('Error parsing stream data:', e);
                                    }
                                }
                            }
                        }
                    }

                    // Get the actual prompt from the input
                    const prompt = promptFieldName ? input[promptFieldName] : 'N/A';

                    // Use the final image URL, or fallback to the last image in the chain
                    const displayImageUrl = finalImageUrl || (lastNodeWithImage ? lastNodeWithImage.url : null) || Array.from(allImageUrls).pop();

                    if (displayImageUrl) {
                        // Save to history
                        const historyEntry = {
                            url: displayImageUrl,
                            timestamp: new Date().toISOString(),
                            isPrivate: isPrivate,
                            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)
                        };

                        console.log(`Saving batch history entry for prompt: ${prompt}`);
                        saveToHistory(historyEntry);

                        return {
                            status: 'success',
                            input: input,  // Include the original input
                            prompt: prompt,
                            finalOutput: displayImageUrl,
                            images: Array.from(allImageUrls),
                            metadata: {
                                nodeOutputs,
                                graphExecutionState,
                                spellRun,
                                rawResponse
                            }
                        };
                    } else {
                        throw new Error('No image generated');
                    }

                } catch (error) {
                    console.error('Batch generation error:', error);
                    return {
                        status: 'error',
                        input: input,  // Include the original input
                        prompt: promptFieldName ? input[promptFieldName] : 'N/A',
                        error: error.message
                    };
                }
            });

            const batchResults = await Promise.all(promises);
            results.push(...batchResults);

            completed += batch.length;
            updateBatchProgress(completed, total);

            // Small delay between batches to prevent overload
            if (i + batchSize < inputs.length) {
                await new Promise(resolve => setTimeout(resolve, 1000));
            }
        }

        return results;
    }

    async function updateBatchProgress(completed, total) {
        const progressContainer = document.querySelector('.batch-progress');
        if (!progressContainer) {
            const container = document.createElement('div');
            container.className = 'batch-progress';
            container.style.cssText = `
                text-align: center;
                margin-top: 16px;
                padding: 12px;
                background: #f3f4f6;
                border-radius: 8px;
                color: #374151;
            `;
            document.getElementById('batchPanel').appendChild(container);
        }
        const percentage = Math.round((completed / total) * 100);
        document.querySelector('.batch-progress').innerHTML = `
            <div style="font-weight: 500; margin-bottom: 4px;">Processing: ${completed}/${total}</div>
            <div style="width: 100%; height: 4px; background: #e5e7eb; border-radius: 2px;">
                <div style="width: ${percentage}%; height: 100%; background: #2563eb; border-radius: 2px; transition: width 0.3s ease;"></div>
            </div>
        `;
    }

    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') {
                inputs.push({
                    name: input.name,
                    type: input.type,
                    placeholder: input.placeholder || input.name
                });
            }
        });
        return inputs;
    }

    function displayBatchPanel() {
        let batchPanel = document.getElementById('batchPanel');
        if (batchPanel) {
            batchPanel.remove();
            return;
        }

        const inputs = getWorkflowInputs();
        if (inputs.length === 0) {
            alert('No inputs detected on this page');
            return;
        }

        batchPanel = document.createElement('div');
        batchPanel.id = 'batchPanel';
        batchPanel.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            padding: 24px;
            border-radius: 16px;
            box-shadow: 0 8px 32px rgba(0,0,0,0.15);
            z-index: 10001;
            max-height: 90vh;
            width: 90vw;
            max-width: 800px;
            overflow-y: auto;
            display: flex;
            flex-direction: column;
            gap: 16px;
        `;

        const header = document.createElement('div');
        header.style.cssText = `
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 16px;
        `;

        const title = document.createElement('h2');
        title.textContent = '🔄 Batch Generator';
        title.style.margin = '0';

        const closeButton = document.createElement('button');
        closeButton.innerHTML = '✕';
        closeButton.style.cssText = `
            border: none;
            background: none;
            font-size: 20px;
            cursor: pointer;
            padding: 4px;
            color: #666;
        `;
        closeButton.onmouseover = () => closeButton.style.color = '#1a1a1a';
        closeButton.onmouseout = () => closeButton.style.color = '#666';
        closeButton.onclick = () => batchPanel.remove();

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

        const inputContainer = document.createElement('div');
        inputContainer.id = 'batchInputContainer';
        inputContainer.style.cssText = `
            margin-bottom: 16px;
            max-height: 400px;
            overflow-y: auto;
        `;

        // Add row controls
        const controls = document.createElement('div');
        controls.style.cssText = `
            display: flex;
            gap: 8px;
            margin-bottom: 16px;
            align-items: center;
        `;

        const addButton = document.createElement('button');
        addButton.innerHTML = '+ Add Row';
        addButton.style.cssText = `
            padding: 8px 16px;
            background: #f3f4f6;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            flex: 1;
        `;

        // Add private/public toggle button
        const toggleButton = document.createElement('button');
        toggleButton.id = 'batchPrivateToggle';
        toggleButton.style.cssText = `
            padding: 8px 16px;
            border-radius: 6px;
            border: none;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s ease;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
            color: #ffffff;
            font-size: 14px;
            min-width: 120px;
            flex-shrink: 0;
        `;

        const updateToggleState = (isPrivate) => {
            toggleButton.innerHTML = isPrivate ? '🔒 Private' : '🌐 Public';
            toggleButton.style.backgroundColor = isPrivate ? '#dc2626' : '#000000';
        };

        const initialState = GM_getValue('isPrivate', true);
        updateToggleState(initialState);

        toggleButton.onclick = (e) => {
            e.preventDefault();
            const newState = !GM_getValue('isPrivate', true);
            GM_setValue('isPrivate', newState);
            updateToggleState(newState);
        };

        controls.appendChild(addButton);
        controls.appendChild(toggleButton);

        // Create initial input fields
        function createInputRow(values = null) {
            const row = document.createElement('div');
            row.className = 'batch-input-row';
            row.style.cssText = `
                display: grid;
                grid-template-columns: repeat(${inputs.length}, 1fr) auto auto;
                gap: 8px;
                margin-bottom: 8px;
            `;

            inputs.forEach(input => {
                const inputField = document.createElement('input');
                inputField.type = input.type;
                inputField.name = input.name;
                inputField.placeholder = input.placeholder;
                if (values && values[input.name]) {
                    inputField.value = values[input.name];
                }
                inputField.style.cssText = `
                    padding: 8px;
                    border: 1px solid #e5e7eb;
                    border-radius: 6px;
                    width: 100%;
                `;
                row.appendChild(inputField);
            });

            const duplicateButton = document.createElement('button');
            duplicateButton.innerHTML = '📋';
            duplicateButton.title = 'Duplicate Row';
            duplicateButton.style.cssText = `
                padding: 8px;
                background: #e5e7eb;
                border: none;
                border-radius: 6px;
                cursor: pointer;
                color: #374151;
            `;
            duplicateButton.onclick = () => {
                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.innerHTML = '✕';
            removeButton.style.cssText = `
                padding: 8px;
                background: #fee2e2;
                border: none;
                border-radius: 6px;
                cursor: pointer;
                color: #dc2626;
            `;
            removeButton.onclick = () => row.remove();

            row.appendChild(duplicateButton);
            row.appendChild(removeButton);
            return row;
        }

        addButton.onclick = () => {
            inputContainer.appendChild(createInputRow());
        };

        const startButton = document.createElement('button');
        startButton.innerHTML = '▶️ Start Batch';
        startButton.style.cssText = `
            padding: 12px;
            background: #2563eb;
            color: white;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            width: 100%;
        `;

        startButton.onclick = async () => {
            let batchInputs = [];
            inputContainer.querySelectorAll('.batch-input-row').forEach(row => {
                const rowInputs = {};
                let hasValue = false;

                row.querySelectorAll('input').forEach(input => {
                    if (input.name && input.value.trim()) {
                        rowInputs[input.name] = input.value.trim();
                        hasValue = true;
                    }
                });

                if (hasValue) {
                    batchInputs.push(rowInputs);
                }
            });

            if (batchInputs.length === 0) {
                alert('Please add at least one input');
                return;
            }

            startButton.disabled = true;
            startButton.innerHTML = '⏳ Processing...';

            try {
                const results = await processBatchGeneration(batchInputs, GM_getValue('isPrivate', true));

                // Clear the input container and show results
                inputContainer.innerHTML = '';
                inputContainer.appendChild(displayBatchResults(results));

                startButton.innerHTML = '✅ Complete';
                setTimeout(() => {
                    startButton.innerHTML = '▶️ Start New Batch';
                    startButton.disabled = false;
                }, 2000);
            } catch (error) {
                startButton.innerHTML = '❌ Error';
                alert('Error processing batch: ' + error.message);
                startButton.disabled = false;
            }
        };

        batchPanel.appendChild(header);
        batchPanel.appendChild(controls);
        batchPanel.appendChild(inputContainer);
        batchPanel.appendChild(startButton);

        // Add initial row
        inputContainer.appendChild(createInputRow());

        document.body.appendChild(batchPanel);
    }

    function displayBatchResults(results) {
        const container = document.createElement('div');
        container.style.cssText = `
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
            gap: 16px;
            margin-top: 16px;
        `;

        const summary = document.createElement('div');
        summary.style.cssText = `
            grid-column: 1 / -1;
            padding: 12px;
            border-radius: 8px;
            background: #f9fafb;
            margin-bottom: 8px;
            font-size: 14px;
        `;

        const successful = results.filter(r => r.status === 'success').length;
        const failed = results.filter(r => r.status === 'error').length;

        summary.innerHTML = `
            <div style="font-weight: 500; margin-bottom: 4px;">Generation Summary</div>
            <div style="color: #059669;">✓ ${successful} successful</div>
            ${failed > 0 ? `<div style="color: #dc2626;">✕ ${failed} failed</div>` : ''}
        `;

        container.appendChild(summary);

        results.forEach(result => {
            const resultCard = document.createElement('div');
            resultCard.style.cssText = `
                border: 1px solid #e5e7eb;
                border-radius: 8px;
                padding: 12px;
                background: ${result.status === 'success' ? '#f3f4f6' : '#fee2e2'};
            `;

            if (result.status === 'success' && result.finalOutput) {
                const img = document.createElement('img');
                img.src = result.finalOutput;
                img.style.cssText = `
                    width: 100%;
                    height: auto;
                    border-radius: 4px;
                    cursor: pointer;
                `;
                img.onclick = () => window.open(result.finalOutput, '_blank');
                resultCard.appendChild(img);
            }

            const details = document.createElement('div');
            details.style.fontSize = '12px';
            details.innerHTML = `
                <div><strong>Status:</strong> ${result.status}</div>
                <div><strong>Prompt:</strong> ${result.prompt || 'N/A'}</div>
                ${result.status === 'error' ? `<div style="color: #dc2626"><strong>Error:</strong> ${result.error}</div>` : ''}
            `;

            resultCard.appendChild(details);
            container.appendChild(resultCard);
        });

        return container;
    }

    function addPrivateToggle() {
        const runButton = Array.from(document.querySelectorAll('button')).find(
            button => button.textContent.includes('Run This Glif')
        );

        if (!runButton || document.getElementById('privateToggle')) return;

        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;
        `;

        const updateButtonState = (isPrivate) => {
            toggle.innerHTML = isPrivate ? '🔒 Private' : '🌐 Public';
            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', true);
            GM_setValue('isPrivate', newState);
            updateButtonState(newState);

            if (!newState) {
                const preview = document.getElementById('privateImagePreview');
                if (preview) preview.remove();
            }
        });

        runButton.parentNode.appendChild(toggle);
    }

    function addToolsToNavbar() {
        const navbarLinks = document.querySelector('.flex.gap-3.md\\:gap-\\[44px\\]');
        if (!navbarLinks || document.getElementById('tools-dropdown')) return;

        const toolsContainer = createToolsDropdown();
        toolsContainer.id = 'tools-dropdown';
        navbarLinks.appendChild(toolsContainer);
    }

    function createToolsDropdown() {
        const toolsContainer = document.createElement('div');
        toolsContainer.style.cssText = `
            position: relative;
            display: inline-block;
        `;

        const toolsButton = document.createElement('button');
        toolsButton.innerHTML = `
            <div class="flex items-center gap-1 text-lg font-bold hover:text-brand-600 active:text-brand-600">
                <span class="block h-2 w-2"></span>Tools
            </div>
        `;

        const dropdownContent = document.createElement('div');
        dropdownContent.style.cssText = `
            display: none;
            position: absolute;
            right: 0;
            background-color: #fff;
            min-width: 200px;
            box-shadow: 0 8px 16px rgba(0,0,0,0.1);
            z-index: 1000;
            border-radius: 6px;
            padding: 8px 0;
            border: 1px solid #e5e7eb;
            margin-top: 8px;
        `;

        const menuItems = [
            { text: '🔄 Batch Generator', onClick: displayBatchPanel },
            { text: '📜 Image History', onClick: displayHistoryPanel },
            { text: '🐛 Report Bug', onClick: displayBugReportForm }
        ];

        menuItems.forEach(item => {
            const menuItem = document.createElement('div');
            menuItem.innerHTML = item.text;
            menuItem.style.cssText = `
                padding: 8px 16px;
                cursor: pointer;
                transition: background-color 0.2s;
                color: #374151;
                font-size: 14px;
            `;
            menuItem.onmouseover = () => menuItem.style.backgroundColor = '#f3f4f6';
            menuItem.onmouseout = () => menuItem.style.backgroundColor = 'transparent';
            menuItem.onclick = (e) => {
                e.stopPropagation();
                item.onClick();
                dropdownContent.style.display = 'none';
            };
            dropdownContent.appendChild(menuItem);
        });

        // Add divider
        const divider = document.createElement('div');
        divider.style.cssText = `
            height: 1px;
            background-color: #e5e7eb;
            margin: 8px 0;
        `;
        dropdownContent.appendChild(divider);

        const credits = document.createElement('div');
        credits.style.cssText = `
            padding: 8px 16px;
            color: #6b7280;
            font-size: 12px;
            text-align: center;
        `;
        credits.innerHTML = `
            <div>GLIF Tools v${GM_info.script.version}</div>
            <div>by <a href="https://glif.app/@appelsiensam/" target="_blank" style="color: #3b82f6; text-decoration: none;">i12bp8</a></div>
        `;
        dropdownContent.appendChild(credits);

        let isOpen = false;
        toolsButton.onclick = (e) => {
            e.stopPropagation();
            isOpen = !isOpen;
            dropdownContent.style.display = isOpen ? 'block' : 'none';
        };

        document.addEventListener('click', () => {
            isOpen = false;
            dropdownContent.style.display = 'none';
        });

        toolsContainer.appendChild(toolsButton);
        toolsContainer.appendChild(dropdownContent);
        return toolsContainer;
    }

    function displayBugReportForm() {
        const modal = document.createElement('div');
        modal.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.5);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 10000;
        `;

        const form = document.createElement('div');
        form.style.cssText = `
            background: white;
            padding: 32px;
            border-radius: 16px;
            width: 90%;
            max-width: 600px;
            max-height: 90vh;
            overflow-y: auto;
            position: relative;
            box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
        `;

        const closeButton = document.createElement('button');
        closeButton.innerHTML = '✕';
        closeButton.style.cssText = `
            position: absolute;
            top: 24px;
            right: 24px;
            background: none;
            border: none;
            font-size: 24px;
            cursor: pointer;
            color: #6b7280;
            padding: 4px;
            border-radius: 4px;
            transition: all 0.2s;
            &:hover {
                background: #f3f4f6;
                color: #1f2937;
            }
        `;
        closeButton.onclick = () => modal.remove();

        const title = document.createElement('h2');
        title.textContent = 'Report Bug / Request Feature';
        title.style.cssText = `
            margin: 0 0 24px 0;
            font-size: 24px;
            font-weight: 600;
            color: #1f2937;
        `;
        form.appendChild(title);

        const typeContainer = document.createElement('div');
        typeContainer.style.cssText = `
            display: flex;
            gap: 12px;
            margin-bottom: 24px;
        `;

        const createTypeButton = (text, type, icon) => {
            const button = document.createElement('button');
            button.innerHTML = `${icon} ${text}`;
            button.dataset.type = type;
            button.style.cssText = `
                flex: 1;
                padding: 12px;
                border: 2px solid #e5e7eb;
                border-radius: 8px;
                background: white;
                cursor: pointer;
                font-size: 16px;
                font-weight: 500;
                color: #374151;
                transition: all 0.2s;
                &:hover {
                    border-color: #3b82f6;
                    color: #3b82f6;
                }
            `;
            button.onclick = () => {
                typeContainer.querySelectorAll('button').forEach(btn => {
                    btn.style.borderColor = '#e5e7eb';
                    btn.style.color = '#374151';
                    btn.style.background = 'white';
                });
                button.style.borderColor = '#3b82f6';
                button.style.color = '#3b82f6';
                button.style.background = '#eff6ff';
                selectedType = type;
            };
            return button;
        };

        let selectedType = 'bug';
        typeContainer.appendChild(createTypeButton('Report Bug', 'bug', '🐛'));
        typeContainer.appendChild(createTypeButton('Request Feature', 'feature', '✨'));

        const createInput = (label, placeholder, isTextarea = false) => {
            const container = document.createElement('div');
            container.style.marginBottom = '20px';

            const labelEl = document.createElement('label');
            labelEl.textContent = label;
            labelEl.style.cssText = `
                display: block;
                margin-bottom: 8px;
                font-weight: 500;
                color: #374151;
            `;

            const input = document.createElement(isTextarea ? 'textarea' : 'input');
            input.placeholder = placeholder;
            input.style.cssText = `
                width: 100%;
                padding: 12px;
                border: 2px solid #e5e7eb;
                border-radius: 8px;
                font-size: 16px;
                transition: border-color 0.2s;
                background: #f9fafb;
                ${isTextarea ? 'height: 150px; resize: vertical;' : ''}
                &:focus {
                    outline: none;
                    border-color: #3b82f6;
                    background: white;
                }
            `;

            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.style.cssText = `
            width: 100%;
            padding: 12px;
            background: #3b82f6;
            color: white;
            border: none;
            border-radius: 8px;
            font-size: 16px;
            font-weight: 500;
            cursor: pointer;
            transition: background 0.2s;
            &:hover {
                background: #2563eb;
            }
            &:disabled {
                background: #93c5fd;
                cursor: not-allowed;
            }
        `;

        submitButton.onclick = async () => {
            const title = titleInput.input.value.trim();
            const description = descriptionInput.input.value.trim();

            if (!title || !description) {
                alert('Please fill in all fields');
                return;
            }

            submitButton.disabled = true;
            submitButton.textContent = '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) {
                    submitButton.textContent = 'Submitted Successfully!';
                    submitButton.style.background = '#059669';
                    setTimeout(() => modal.remove(), 1500);
                } else {
                    throw new Error('Failed to submit');
                }
            } catch (error) {
                submitButton.disabled = false;
                submitButton.textContent = 'Submit';
                alert('Failed to submit. Please try again.');
            }
        };

        form.appendChild(closeButton);
        form.appendChild(title);
        form.appendChild(typeContainer);
        form.appendChild(titleInput.container);
        form.appendChild(descriptionInput.container);
        form.appendChild(submitButton);

        modal.appendChild(form);
        document.body.appendChild(modal);
    }

    const observer = new MutationObserver((mutations, obs) => {
        const navbar = document.querySelector('.flex.gap-3.md\\:gap-\\[44px\\]');
        if (navbar) {
            addToolsToNavbar();
            addPrivateToggle();
        }
    });

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

    // Initial check
    if (document.readyState === 'complete') {
        addToolsToNavbar();
        addPrivateToggle();
    } else {
        window.addEventListener('load', () => {
            addToolsToNavbar();
            addPrivateToggle();
        });
    }
})();