<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GAZE</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link href="{{ url_for('static', filename='style.css') }}" rel="stylesheet">
</head>
<body>
    <nav class="navbar navbar-dark mb-4">
        <div class="container">
            <a class="navbar-brand d-flex align-items-center gap-2" href="/"><img src="{{ url_for('static', filename='icons/gaze-logo.png') }}" class="navbar-logo">GAZE</a>
            <div class="d-flex align-items-center gap-1 flex-wrap">
                <a href="/" class="btn btn-sm btn-outline-light">Characters</a>
                <a href="/outfits" class="btn btn-sm btn-outline-light">Outfits</a>
                <a href="/looks" class="btn btn-sm btn-outline-light">Looks</a>
                <a href="/actions" class="btn btn-sm btn-outline-light">Actions</a>
                <a href="/styles" class="btn btn-sm btn-outline-light">Styles</a>
                <a href="/scenes" class="btn btn-sm btn-outline-light">Scenes</a>
                <a href="/detailers" class="btn btn-sm btn-outline-light">Detailers</a>
                <a href="/checkpoints" class="btn btn-sm btn-outline-light">Checkpoints</a>
                <a href="/presets" class="btn btn-sm btn-outline-light">Presets</a>
                <div class="vr mx-1 d-none d-lg-block"></div>
                <a href="/generator" class="btn btn-sm btn-outline-light">Generator</a>
                <a href="/quick" class="btn btn-sm btn-outline-light">Quick</a>
                <a href="/multi" class="btn btn-sm btn-outline-light">Multi</a>
                <a href="/gallery" class="btn btn-sm btn-outline-light">Image Gallery</a>
                <a href="/settings" class="btn btn-sm btn-outline-light">Settings</a>
                <div class="vr mx-1 d-none d-lg-block"></div>
                <!-- Search -->
                <form action="/search" method="get" class="d-flex" style="max-width:180px;">
                    <input type="text" name="q" class="form-control form-control-sm bg-dark text-light border-secondary" placeholder="Search..." aria-label="Search">
                </form>
                <div class="vr mx-1 d-none d-lg-block"></div>
                <!-- Queue indicator -->
                <button id="queue-btn" class="btn btn-sm queue-btn" data-bs-toggle="modal" data-bs-target="#queueModal" title="Job Queue">
                    <span class="queue-icon">⏳</span>
                    <span id="queue-count-badge" class="queue-badge d-none">0</span>
                </button>
                <div class="vr mx-1 d-none d-lg-block"></div>
                <!-- Service status indicators -->
                <span id="status-comfyui" class="service-status" title="ComfyUI" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="ComfyUI: checking…">
                    <span class="status-dot status-checking"></span>
                    <span class="status-label d-none d-xl-inline">ComfyUI</span>
                </span>
                <span id="status-mcp" class="service-status" title="MCP" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="MCP: checking…">
                    <span class="status-dot status-checking"></span>
                    <span class="status-label d-none d-xl-inline">MCP</span>
                </span>
                <span id="status-llm" class="service-status" title="LLM" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="LLM: checking…">
                    <span class="status-dot status-checking"></span>
                    <span class="status-label d-none d-xl-inline">LLM</span>
                </span>
            </div>
        </div>
    </nav>

    <div class="container">
        {% with messages = get_flashed_messages() %}
          {% if messages %}
            {% for message in messages %}
              <div class="alert alert-info">{{ message }}</div>
            {% endfor %}
          {% endif %}
        {% endwith %}

        {% block content %}{% endblock %}
    </div>

<!-- Resource delete modal (shared across category gallery pages) -->
<div class="modal fade" id="resourceDeleteModal" tabindex="-1">
  <div class="modal-dialog modal-dialog-centered">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Delete <span id="rdm-name"></span></h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">
        <div class="d-grid gap-2">
          <button class="btn btn-outline-secondary text-start" onclick="confirmResourceDelete('soft')">
            <strong>Soft delete</strong> — remove entry from this gallery<br>
            <small class="text-muted">JSON data file deleted; LoRA/checkpoint file kept on disk</small>
          </button>
          <button class="btn btn-outline-danger text-start" onclick="confirmResourceDelete('hard')">
            <strong>Hard delete</strong> — remove entry and all associated files<br>
            <small class="text-muted">Deletes JSON data file and LoRA/checkpoint safetensors from disk. Irreversible.</small>
          </button>
        </div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
      </div>
    </div>
  </div>
</div>

<!-- Job Queue Modal -->
<div class="modal fade" id="queueModal" tabindex="-1">
  <div class="modal-dialog modal-dialog-centered modal-lg">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">
          Job Queue
          <span id="queue-modal-count" class="badge bg-secondary ms-2">0</span>
        </h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body p-0">
        <div id="queue-empty-msg" class="text-center text-muted py-4">
          <p class="mb-0">No jobs in queue.</p>
        </div>
        <ul id="queue-job-list" class="list-group list-group-flush d-none"></ul>
      </div>
      <div class="modal-footer">
        <small class="text-muted me-auto">Jobs are processed sequentially. Close this window to continue browsing.</small>
        <button type="button" id="queue-clear-btn" class="btn btn-warning btn-sm" onclick="queueClearAll()">
          🗑️ Clear Queue
        </button>
        <button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Close</button>
      </div>
    </div>
  </div>
</div>

<!-- Gallery Navigation Modal -->
<div class="modal fade" id="galleryModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-xl modal-dialog-centered">
    <div class="modal-content bg-transparent border-0">
      <div class="modal-body p-0 text-center position-relative">
        <!-- Navigation Arrows -->
        <button type="button" id="gallery-prev" aria-label="Previous image" class="btn btn-dark btn-lg position-absolute start-0 top-50 translate-middle-y ms-2 opacity-75 hover-opacity-100"
                style="z-index: 1050; border-radius: 50%; width: 50px; height: 50px; display: flex; align-items: center; justify-content: center;">
          &#8249;
        </button>
        <button type="button" id="gallery-next" aria-label="Next image" class="btn btn-dark btn-lg position-absolute end-0 top-50 translate-middle-y me-2 opacity-75 hover-opacity-100"
                style="z-index: 1050; border-radius: 50%; width: 50px; height: 50px; display: flex; align-items: center; justify-content: center;">
          &#8250;
        </button>
        <!-- Image Counter -->
        <div id="gallery-counter" class="position-absolute bottom-0 start-50 translate-middle-x mb-3 px-3 py-1 bg-dark rounded text-white opacity-75"
             style="z-index: 1050; font-size: 0.9rem;">
          1 / 1
        </div>
        <!-- Main Image -->
        <img id="galleryImage" src="" alt="Gallery Image" class="img-fluid" style="max-height: 90vh; cursor: default;">
      </div>
    </div>
  </div>
</div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => new bootstrap.Tooltip(el));
        });

        // ---- Loaded checkpoint → ComfyUI tooltip ----
        (function() {
            let _loadedCheckpoint = null;
            async function pollLoadedCheckpoint() {
                try {
                    const r = await fetch('/api/comfyui/loaded_checkpoint', { cache: 'no-store' });
                    const data = await r.json();
                    _loadedCheckpoint = data.checkpoint || null;
                } catch {
                    _loadedCheckpoint = null;
                }
                updateComfyTooltip();
            }
            function updateComfyTooltip() {
                const el = document.getElementById('status-comfyui');
                if (!el) return;
                const dot = el.querySelector('.status-dot');
                const online = dot && dot.classList.contains('status-ok');
                let text = 'ComfyUI: ' + (online ? 'online' : 'offline');
                if (_loadedCheckpoint) {
                    const parts = _loadedCheckpoint.split(/[/\\]/);
                    const name = parts[parts.length - 1].replace(/\.safetensors$/, '');
                    text += '\n' + name;
                }
                el.setAttribute('data-bs-title', text);
                el.setAttribute('title', text);
                const tip = bootstrap.Tooltip.getInstance(el);
                if (tip) tip.setContent({ '.tooltip-inner': text });
            }
            // Hook into the existing status polling to refresh tooltip after status changes
            window._updateComfyTooltip = updateComfyTooltip;
            document.addEventListener('DOMContentLoaded', () => {
                pollLoadedCheckpoint();
                setInterval(pollLoadedCheckpoint, 30000);
            });
        })();
    </script>
    <script>
    // ---- Resource delete modal (category galleries) ----
    let _rdmCategory = '', _rdmSlug = '';
    let resourceDeleteModal;
    document.addEventListener('DOMContentLoaded', () => {
      const el = document.getElementById('resourceDeleteModal');
      if (el) resourceDeleteModal = new bootstrap.Modal(el);

      // Attach listeners to resource delete buttons via data attrs (avoids inline onclick issues)
      document.querySelectorAll('.resource-delete-btn').forEach(btn => {
        btn.addEventListener('click', function(e) {
          e.stopPropagation();
          openResourceDeleteModal(this.dataset.category, this.dataset.slug, this.dataset.name);
        });
      });
    });
    function openResourceDeleteModal(category, slug, name) {
      _rdmCategory = category;
      _rdmSlug = slug;
      document.getElementById('rdm-name').textContent = name;
      resourceDeleteModal.show();
    }
    </script>
    <script src="/static/js/layout-utils.js"></script>
    <script>
    // ---- Service status indicators ----
    (function () {
        const services = [
            { id: 'status-comfyui',     url: '/api/status/comfyui',     label: 'ComfyUI' },
            { id: 'status-llm',         url: '/api/status/llm',         label: 'LLM' },
        ];

        // MCP services (checked separately for combined indicator)
        const mcpServices = [
            { url: '/api/status/mcp',         label: 'Danbooru MCP', key: 'danbooru' },
            { url: '/api/status/character-mcp', label: 'Character MCP', key: 'character' },
        ];

        // Global service status tracker
        window.serviceStatus = {
            comfyui: false,
            mcp: false,
            characterMcp: false,
            llm: false,
            mcpStatuses: {
                danbooru: false,
                character: false
            }
        };

        function updateButtons() {
            // Image generation buttons require ComfyUI
            const imageGenButtons = document.querySelectorAll('.btn-image-gen, [data-requires="comfyui"], #generate-btn, button[form="generate-form"], #batch-generate-btn, #regenerate-all-btn, #generate-all-btn');
            imageGenButtons.forEach(btn => {
                if (!window.serviceStatus.comfyui) {
                    btn.disabled = true;
                    btn.title = btn.title || 'ComfyUI is not available';
                } else {
                    btn.disabled = false;
                }
            });

            // JSON generation buttons require Danbooru MCP or LLM
            const jsonGenButtons = document.querySelectorAll('.btn-json-gen, [data-requires="mcp-llm"], button[data-bs-target="#jsonEditorModal"], #json-save-btn');
            jsonGenButtons.forEach(btn => {
                if (!window.serviceStatus.mcp && !window.serviceStatus.llm) {
                    btn.disabled = true;
                    btn.title = btn.title || 'Danbooru MCP or LLM is required';
                } else {
                    btn.disabled = false;
                }
            });

            // Character generation buttons require Character MCP or LLM
            const charGenButtons = document.querySelectorAll('.btn-char-gen, [data-requires="char-mcp-llm"], #submit-btn, button[form*="create"]');
            charGenButtons.forEach(btn => {
                if (!window.serviceStatus.characterMcp && !window.serviceStatus.llm) {
                    btn.disabled = true;
                    btn.title = btn.title || 'Character MCP or LLM is required';
                } else {
                    btn.disabled = false;
                }
            });
        }

        function updateMcpStatus() {
            const el = document.getElementById('status-mcp');
            if (!el) return;
            
            const statuses = window.serviceStatus.mcpStatuses;
            const allOnline = statuses.danbooru && statuses.character;
            const allOffline = !statuses.danbooru && !statuses.character;
            const someOffline = !allOnline && !allOffline;
            
            // Update global status flags
            window.serviceStatus.mcp = statuses.danbooru;
            window.serviceStatus.characterMcp = statuses.character;
            
            const dot = el.querySelector('.status-dot');
            
            // Set status class: red (all offline), yellow (some offline), green (all online)
            if (allOffline) {
                dot.className = 'status-dot status-error';
            } else if (someOffline) {
                dot.className = 'status-dot status-warning';
            } else {
                dot.className = 'status-dot status-ok';
            }
            
            // Build detailed tooltip
            const tooltipLines = [
                'MCP Services:',
                `Danbooru MCP: ${statuses.danbooru ? 'online' : 'offline'}`,
                `Character MCP: ${statuses.character ? 'online' : 'offline'}`
            ];
            const tooltipText = tooltipLines.join('\n');
            
            el.setAttribute('data-bs-title', tooltipText);
            el.setAttribute('title', tooltipText);
            const tip = bootstrap.Tooltip.getInstance(el);
            if (tip) tip.setContent({ '.tooltip-inner': tooltipText });
            
            // Update buttons based on new status
            updateButtons();
        }

        function setStatus(id, label, ok) {
            const el = document.getElementById(id);
            if (!el) return;
            const dot = el.querySelector('.status-dot');
            dot.className = 'status-dot ' + (ok ? 'status-ok' : 'status-error');
            if (id === 'status-comfyui' && window._updateComfyTooltip) {
                window._updateComfyTooltip();
            }
            const tooltipText = label + ': ' + (ok ? 'online' : 'offline');
            el.setAttribute('data-bs-title', tooltipText);
            el.setAttribute('title', tooltipText);
            const tip = bootstrap.Tooltip.getInstance(el);
            if (tip) tip.setContent({ '.tooltip-inner': tooltipText });

            // Update global status
            if (id === 'status-comfyui') window.serviceStatus.comfyui = ok;
            if (id === 'status-llm') window.serviceStatus.llm = ok;

            // Update buttons based on new status
            updateButtons();
        }

        async function pollService(svc) {
            try {
                const r = await fetch(svc.url, { cache: 'no-store' });
                const data = await r.json();
                setStatus(svc.id, svc.label, data.status === 'ok');
            } catch {
                setStatus(svc.id, svc.label, false);
            }
        }

        async function pollMcpService(svc) {
            try {
                const r = await fetch(svc.url, { cache: 'no-store' });
                const data = await r.json();
                window.serviceStatus.mcpStatuses[svc.key] = data.status === 'ok';
            } catch {
                window.serviceStatus.mcpStatuses[svc.key] = false;
            }
        }

        async function pollAll() {
            // Poll regular services
            await Promise.all(services.map(pollService));
            
            // Poll MCP services
            await Promise.all(mcpServices.map(pollMcpService));
            
            // Update combined MCP status
            updateMcpStatus();
        }

        document.addEventListener('DOMContentLoaded', () => {
            pollAll();
            setInterval(pollAll, 30000); // re-check every 30 s
        });
    })();
    </script>
    <script>
    // ---- Job Queue UI ----
    (function() {
        const badge = document.getElementById('queue-count-badge');
        const modalCount = document.getElementById('queue-modal-count');
        const jobList = document.getElementById('queue-job-list');
        const emptyMsg = document.getElementById('queue-empty-msg');

        const STATUS_LABELS = {
            pending: { text: 'Pending', cls: 'text-muted' },
            processing: { text: 'Generating…', cls: 'text-warning' },
            paused: { text: 'Paused', cls: 'text-secondary' },
            done: { text: 'Done', cls: 'text-success' },
            failed: { text: 'Failed', cls: 'text-danger' },
            removed: { text: 'Removed', cls: 'text-muted' },
        };

        function renderQueue(jobs) {
            const activeJobs = jobs.filter(j => !['done', 'failed', 'removed'].includes(j.status));
            const pendingJobs = jobs.filter(j => j.status === 'pending');
            const processingJob = jobs.find(j => j.status === 'processing');
            const count = activeJobs.length;
            const queueBtn = document.getElementById('queue-btn');

            // Update badge
            if (count > 0) {
                badge.textContent = count;
                badge.classList.remove('d-none');
                queueBtn.classList.add('queue-btn-active');
            } else {
                badge.classList.add('d-none');
                queueBtn.classList.remove('queue-btn-active');
            }

            // Update generating animation and tooltip
            if (processingJob) {
                queueBtn.classList.add('queue-btn-generating');
                queueBtn.title = `Generating: ${processingJob.label}`;
            } else if (pendingJobs.length > 0) {
                queueBtn.classList.remove('queue-btn-generating');
                queueBtn.title = `${pendingJobs.length} job(s) queued`;
            } else {
                queueBtn.classList.remove('queue-btn-generating');
                queueBtn.title = 'Job Queue';
            }

            // Update modal count
            if (modalCount) modalCount.textContent = jobs.length;

            // Update Clear Queue button state
            const clearBtn = document.getElementById('queue-clear-btn');
            if (clearBtn) {
                if (pendingJobs.length > 0) {
                    clearBtn.disabled = false;
                    clearBtn.title = `Clear ${pendingJobs.length} pending job(s)`;
                } else {
                    clearBtn.disabled = true;
                    clearBtn.title = 'No pending jobs to clear';
                }
            }

            // Render job list
            if (!jobList) return;
            if (jobs.length === 0) {
                jobList.classList.add('d-none');
                if (emptyMsg) emptyMsg.classList.remove('d-none');
                return;
            }
            jobList.classList.remove('d-none');
            if (emptyMsg) emptyMsg.classList.add('d-none');

            jobList.innerHTML = '';
            jobs.forEach(job => {
                const li = document.createElement('li');
                li.className = 'list-group-item d-flex align-items-center gap-2 py-2';
                li.id = `queue-job-${job.id}`;

                const statusInfo = STATUS_LABELS[job.status] || { text: job.status, cls: 'text-muted' };

                // Status indicator
                const statusDot = document.createElement('span');
                statusDot.className = `queue-status-dot queue-status-${job.status}`;
                li.appendChild(statusDot);

                // Job type badge
                if (job.job_type === 'llm') {
                    const typeBadge = document.createElement('span');
                    typeBadge.className = 'badge bg-info';
                    typeBadge.textContent = 'LLM';
                    typeBadge.style.fontSize = '0.6rem';
                    li.appendChild(typeBadge);
                }

                // Label
                const label = document.createElement('span');
                label.className = 'flex-grow-1 small';
                label.textContent = job.label;
                li.appendChild(label);

                // Status text
                const statusText = document.createElement('span');
                statusText.className = `small ${statusInfo.cls}`;
                statusText.textContent = statusInfo.text;
                if (job.status === 'failed' && job.error) {
                    statusText.title = job.error;
                    statusText.style.cursor = 'help';
                }
                li.appendChild(statusText);

                // Action buttons
                const btnGroup = document.createElement('div');
                btnGroup.className = 'd-flex gap-1';

                if (job.status === 'pending') {
                    const pauseBtn = document.createElement('button');
                    pauseBtn.className = 'btn btn-xs btn-outline-secondary';
                    pauseBtn.textContent = '⏸';
                    pauseBtn.title = 'Pause';
                    pauseBtn.onclick = () => queuePause(job.id);
                    btnGroup.appendChild(pauseBtn);
                }
                if (job.status === 'paused') {
                    const resumeBtn = document.createElement('button');
                    resumeBtn.className = 'btn btn-xs btn-outline-success';
                    resumeBtn.textContent = '▶';
                    resumeBtn.title = 'Resume';
                    resumeBtn.onclick = () => queuePause(job.id);
                    btnGroup.appendChild(resumeBtn);
                }
                if (['pending', 'paused', 'failed'].includes(job.status)) {
                    const removeBtn = document.createElement('button');
                    removeBtn.className = 'btn btn-xs btn-outline-danger';
                    removeBtn.textContent = '✕';
                    removeBtn.title = 'Remove';
                    removeBtn.onclick = () => queueRemove(job.id);
                    btnGroup.appendChild(removeBtn);
                }

                li.appendChild(btnGroup);
                jobList.appendChild(li);
            });
        }

        async function fetchQueue() {
            try {
                const resp = await fetch('/api/queue');
                const data = await resp.json();
                renderQueue(data.jobs || []);
            } catch (e) {}
        }

        async function queueRemove(jobId) {
            try {
                await fetch(`/api/queue/${jobId}/remove`, { method: 'POST' });
                fetchQueue();
            } catch (e) {}
        }

        async function queuePause(jobId) {
            try {
                await fetch(`/api/queue/${jobId}/pause`, { method: 'POST' });
                fetchQueue();
            } catch (e) {}
        }

        async function queueClearAll() {
            if (!confirm('Clear all pending jobs from the queue? The current generation will continue.')) {
                return;
            }
            try {
                const resp = await fetch('/api/queue/clear', { method: 'POST' });
                const data = await resp.json();
                if (data.removed_count > 0) {
                    alert(`Cleared ${data.removed_count} pending job(s) from the queue.`);
                } else {
                    alert('No pending jobs to clear.');
                }
                fetchQueue();
            } catch (e) {
                alert('Error clearing queue: ' + e.message);
            }
        }

        // Make queueClearAll globally accessible
        window.queueClearAll = queueClearAll;

        // Poll queue every 2 seconds
        document.addEventListener('DOMContentLoaded', () => {
            fetchQueue();
            setInterval(fetchQueue, 2000);

            // Refresh when modal opens
            const queueModal = document.getElementById('queueModal');
            if (queueModal) {
                queueModal.addEventListener('show.bs.modal', fetchQueue);
            }
        });
    })();
    </script>
    <script>
    // ---- Gallery Navigation System ----
    (function() {
        let galleryImages = [];
        let currentIndex = 0;
        let galleryModal = null;

        document.addEventListener('DOMContentLoaded', () => {
            const modalEl = document.getElementById('galleryModal');
            if (modalEl) {
                galleryModal = new bootstrap.Modal(modalEl);

                // Previous button
                document.getElementById('gallery-prev').addEventListener('click', (e) => {
                    e.stopPropagation();
                    navigateGallery(-1);
                });

                // Next button
                document.getElementById('gallery-next').addEventListener('click', (e) => {
                    e.stopPropagation();
                    navigateGallery(1);
                });

                // Keyboard navigation
                modalEl.addEventListener('keydown', (e) => {
                    if (e.key === 'ArrowLeft') {
                        e.preventDefault();
                        navigateGallery(-1);
                    } else if (e.key === 'ArrowRight') {
                        e.preventDefault();
                        navigateGallery(1);
                    } else if (e.key === 'Escape') {
                        galleryModal.hide();
                    }
                });

                // Click on image to go next (optional convenience)
                document.getElementById('galleryImage').addEventListener('click', () => {
                    navigateGallery(1);
                });
            }
        });

        function navigateGallery(direction) {
            if (galleryImages.length === 0) return;

            currentIndex += direction;

            // Wrap around
            if (currentIndex < 0) {
                currentIndex = galleryImages.length - 1;
            } else if (currentIndex >= galleryImages.length) {
                currentIndex = 0;
            }

            updateGalleryImage();
        }

        function updateGalleryImage() {
            const img = document.getElementById('galleryImage');
            const counter = document.getElementById('gallery-counter');

            if (galleryImages[currentIndex]) {
                img.src = galleryImages[currentIndex];
                counter.textContent = `${currentIndex + 1} / ${galleryImages.length}`;
            }
        }

        // Global function to open gallery
        window.openGallery = function(images, startIndex = 0) {
            galleryImages = images || [];
            currentIndex = startIndex;

            if (galleryImages.length === 0) return;

            updateGalleryImage();
            galleryModal.show();
        };

        // Global function to register gallery images from a container
        window.registerGallery = function(containerSelector, imageSelector) {
            const container = document.querySelector(containerSelector);
            if (!container) return [];

            const images = [];
            container.querySelectorAll(imageSelector).forEach((img, index) => {
                const src = img.src || img.dataset.src;
                if (src) {
                    images.push(src);
                    img.style.cursor = 'pointer';
                    img.addEventListener('click', () => {
                        openGallery(images, index);
                    });
                }
            });
            return images;
        };
    })();
    </script>
    <script>
    // Seed input: clear button and auto-populate from generation result
    document.addEventListener('DOMContentLoaded', () => {
        const seedInput = document.getElementById('seed-input');
        const seedClearBtn = document.getElementById('seed-clear-btn');
        if (seedClearBtn && seedInput) {
            seedClearBtn.addEventListener('click', () => { seedInput.value = ''; });
        }
    });
    // Global helper: update seed input after a generation job completes
    function updateSeedFromResult(jobResult) {
        const seedInput = document.getElementById('seed-input');
        if (seedInput && jobResult?.seed != null) {
            seedInput.value = jobResult.seed;
        }
    }
    </script>
    <script>
    // Endless generation mode
    (function() {
        let _endless = false;
        let _endlessCount = 0;
        let _endlessAbort = null;

        window._endlessActive = () => _endless;

        window._endlessStart = function() {
            const form = document.getElementById('generate-form');
            const btn = document.getElementById('endless-btn');
            const stopBtn = document.getElementById('endless-stop-btn');
            const counter = document.getElementById('endless-counter');
            const progressContainer = document.getElementById('progress-container');
            const progressBar = document.getElementById('progress-bar');
            const progressLabel = document.getElementById('progress-label');
            if (!form || !btn) return;

            _endless = true;
            _endlessCount = 0;
            _endlessAbort = new AbortController();
            btn.classList.add('d-none');
            stopBtn.classList.remove('d-none');
            counter.classList.remove('d-none');
            counter.textContent = '0 generated';

            // Disable single generate button during endless
            const genBtn = form.querySelector('button[value="preview"]');
            if (genBtn) genBtn.disabled = true;

            (async function loop() {
                while (_endless) {
                    // Clear seed for random each time
                    const seedInput = document.getElementById('seed-input');
                    if (seedInput) seedInput.value = '';

                    const formData = new FormData(form);
                    formData.set('action', 'preview');

                    // Show progress
                    if (progressContainer) {
                        progressContainer.classList.remove('d-none');
                        progressBar.style.width = '100%';
                        progressBar.textContent = '';
                        progressBar.classList.add('progress-bar-striped', 'progress-bar-animated');
                        progressLabel.textContent = `Endless #${_endlessCount + 1} — Queuing…`;
                    }

                    try {
                        const response = await fetch(form.getAttribute('action'), {
                            method: 'POST', body: formData,
                            headers: { 'X-Requested-With': 'XMLHttpRequest' },
                            signal: _endlessAbort.signal
                        });
                        const data = await response.json();
                        if (data.error) { console.error('Endless error:', data.error); break; }

                        if (progressLabel) progressLabel.textContent = `Endless #${_endlessCount + 1} — Generating…`;

                        // Poll for completion
                        const jobResult = await new Promise((resolve, reject) => {
                            const poll = setInterval(async () => {
                                if (!_endless) { clearInterval(poll); reject(new Error('stopped')); return; }
                                try {
                                    const resp = await fetch(`/api/queue/${data.job_id}/status`);
                                    const status = await resp.json();
                                    if (status.status === 'done') { clearInterval(poll); resolve(status); }
                                    else if (status.status === 'failed' || status.status === 'removed') {
                                        clearInterval(poll); reject(new Error(status.error || 'Job failed'));
                                    } else if (status.status === 'processing' && progressLabel) {
                                        progressLabel.textContent = `Endless #${_endlessCount + 1} — Generating…`;
                                    }
                                } catch (err) { /* keep polling */ }
                            }, 1500);
                        });

                        _endlessCount++;
                        counter.textContent = `${_endlessCount} generated`;

                        // Update preview via page-specific handler
                        if (jobResult.result?.image_url && window._onEndlessResult) {
                            window._onEndlessResult(jobResult);
                        }
                        updateSeedFromResult(jobResult.result);

                    } catch (err) {
                        if (err.name === 'AbortError' || err.message === 'stopped') break;
                        console.error('Endless generation error:', err);
                        break;
                    }
                }
                // Cleanup
                window._endlessStop();
            })();
        };

        window._endlessStop = function() {
            _endless = false;
            if (_endlessAbort) { _endlessAbort.abort(); _endlessAbort = null; }
            const btn = document.getElementById('endless-btn');
            const stopBtn = document.getElementById('endless-stop-btn');
            const progressContainer = document.getElementById('progress-container');
            const progressBar = document.getElementById('progress-bar');
            const form = document.getElementById('generate-form');
            if (btn) btn.classList.remove('d-none');
            if (stopBtn) stopBtn.classList.add('d-none');
            if (progressContainer) progressContainer.classList.add('d-none');
            if (progressBar) progressBar.classList.remove('progress-bar-striped', 'progress-bar-animated');
            const genBtn = form?.querySelector('button[value="preview"]');
            if (genBtn) genBtn.disabled = false;
        };

        // Stop on page leave
        window.addEventListener('beforeunload', () => { if (_endless) window._endlessStop(); });
    })();
    </script>
    {% block scripts %}{% endblock %}
</body>
</html>
