| |
| |
| |
| |
|
|
| (function () { |
| 'use strict'; |
|
|
| var ws = null; |
| var sessionId = ''; |
| var reconnectTimer = null; |
| var isConnecting = false; |
| var currentImages = []; |
| |
| if (document && document.body) { |
| sessionId = document.body.dataset.sessionId || ''; |
| } |
| |
| var connectWebSocket = function() { |
| if (isConnecting || (ws && ws.readyState === 1)) return; |
| |
| isConnecting = true; |
| var protocol = window.location.protocol === 'https:' |
| ? 'wss:' : 'ws:'; |
| var wsUrl = protocol + '//' + window.location.host; |
| |
| ws = new WebSocket(wsUrl); |
| |
| ws.onopen = function() { |
| isConnecting = false; |
| if (reconnectTimer) { |
| clearTimeout(reconnectTimer); |
| reconnectTimer = null; |
| } |
| |
| ws.send(JSON.stringify({ |
| type: 'register', |
| sessionId: sessionId |
| })); |
| }; |
| |
| ws.onmessage = function(event) { |
| try { |
| var data = JSON.parse(event.data); |
| |
| if (!data.sessionId || |
| data.sessionId !== sessionId) { |
| return; |
| } |
| |
| handleWebSocketMessage(data); |
| } catch (e) {} |
| }; |
| |
| ws.onclose = function() { |
| isConnecting = false; |
| reconnectWebSocket(); |
| }; |
| |
| ws.onerror = function() { |
| isConnecting = false; |
| if (ws) ws.close(); |
| }; |
| }; |
| |
| var reconnectWebSocket = function() { |
| if (reconnectTimer) return; |
| |
| reconnectTimer = setTimeout(function() { |
| reconnectTimer = null; |
| connectWebSocket(); |
| }, 1000); |
| }; |
| |
| var handleWebSocketMessage = function(data) { |
| if (!data || !data.type) return; |
| |
| var handlers = { |
| 'progressUpdate': function() { |
| updateProgressUI(data.progress); |
| }, |
| 'generationStarted': showGeneratingUI, |
| 'generationComplete': function() { |
| handleGenerationComplete(data.images); |
| }, |
| 'generationError': function() { |
| handleGenerationError(data.error); |
| }, |
| 'generationCancelled': handleGenerationCancelled, |
| 'imageDeleted': function() { |
| handleImageDeleted(data.images); |
| } |
| }; |
| |
| if (handlers[data.type]) { |
| handlers[data.type](); |
| } |
| }; |
| |
| var updateProgressUI = function(progress) { |
| var progressFill = document.querySelector( |
| '.progress-fill' |
| ); |
| var progressText = document.querySelector( |
| '.progress-text' |
| ); |
| |
| if (progressFill) { |
| progressFill.style.width = progress + '%'; |
| } |
| |
| if (progressText) { |
| progressText.textContent = |
| Math.floor(progress) + '% Complete'; |
| } |
| }; |
| |
| var toggleFormInputs = function(disabled) { |
| var form = document.getElementById('generateForm'); |
| var inputs = form ? form.querySelectorAll( |
| 'input, select, textarea' |
| ) : []; |
| |
| Array.prototype.forEach.call(inputs, function(input) { |
| input.disabled = disabled; |
| }); |
| }; |
| |
| var showGeneratingUI = function() { |
| var outputSection = document.querySelector( |
| '.image-output-section' |
| ); |
| |
| toggleFormInputs(true); |
| |
| if (outputSection) { |
| outputSection.classList.remove('has-images'); |
| outputSection.innerHTML = [ |
| '<div class="loading-container">', |
| '<div class="loading-spinner" ', |
| 'style="margin: 0 auto 20px;"></div>', |
| '<p class="loading-text">', |
| 'Generating your image...</p>', |
| '<div class="progress-bar">', |
| '<div class="progress-fill" ', |
| 'style="width: 0%;"></div>', |
| '</div>', |
| '<p class="progress-text">0% Complete</p>', |
| '</div>' |
| ].join(''); |
| } |
| |
| updateButtonsForGeneration(true); |
| }; |
| |
| var hideGeneratingUI = function() { |
| toggleFormInputs(false); |
| updateButtonsForGeneration(false); |
| |
| if (window.validateInputs) { |
| window.validateInputs(); |
| } |
| }; |
| |
| var resetToInitialState = function() { |
| hideGeneratingUI(); |
| |
| if (currentImages && currentImages.length > 0) { |
| displayImages(currentImages); |
| } else { |
| showPlaceholder(); |
| } |
| }; |
| |
| var showPlaceholder = function() { |
| var outputSection = document.querySelector( |
| '.image-output-section' |
| ); |
| |
| if (!outputSection) return; |
| |
| outputSection.classList.remove('has-images'); |
| outputSection.innerHTML = [ |
| '<svg class="placeholder-icon" ', |
| 'width="80" height="80" ', |
| 'viewBox="0 0 24 24" fill="none">', |
| '<path d="M21 3H3C2 3 1 4 1 5V19C1 20 2 21 ', |
| '3 21H21C22 21 23 20 23 19V5C23 4 22 3 21 3Z', |
| 'M21 19H3V5H21V19Z" fill="currentColor"/>', |
| '<path d="M4.5 16.5L9 12L11.5 14.5L16 10L', |
| '19.5 13.5" stroke="currentColor" ', |
| 'stroke-width="1.5" stroke-linecap="round"/>', |
| '<circle cx="8" cy="8.5" r="1.5" ', |
| 'fill="currentColor"/>', |
| '</svg>', |
| '<p class="placeholder-text">', |
| 'No images generated yet. ', |
| 'Start creating amazing visuals!', |
| '</p>' |
| ].join(''); |
| }; |
| |
| var createButton = function(type, isGenerating) { |
| var icons = { |
| stop: '<rect x="4" y="4" width="16" height="16" ' + |
| 'rx="3" fill="currentColor"/>', |
| play: '<path d="M3 20V4L22 12L3 20ZM5 17L16.85 ' + |
| '12L5 7V10.5L11 12L5 13.5V17Z" ' + |
| 'fill="currentColor"/>' |
| }; |
| |
| if (isGenerating) { |
| return [ |
| '<button type="button" ', |
| 'onclick="cancelGeneration()" ', |
| 'class="btn btn-danger">', |
| '<svg class="button-icon" viewBox="0 0 24 24" ', |
| 'fill="none">', |
| icons.stop, |
| '</svg>', |
| 'Stop Generation', |
| '</button>' |
| ].join(''); |
| } |
| |
| return [ |
| '<button type="submit" id="submitBtn" disabled ', |
| 'class="btn btn-primary">', |
| '<svg class="button-icon" viewBox="0 0 24 24" ', |
| 'fill="none">', |
| icons.play, |
| '</svg>', |
| 'Generate Image', |
| '</button>' |
| ].join(''); |
| }; |
| |
| var updateButtonsForGeneration = function(isGenerating) { |
| var buttonsContainer = document.querySelector( |
| '.flex.justify-center.gap-4' |
| ); |
| |
| if (!buttonsContainer) return; |
| |
| buttonsContainer.innerHTML = createButton( |
| isGenerating ? 'stop' : 'play', |
| isGenerating |
| ); |
| }; |
| |
| var handleGenerationComplete = function(images) { |
| currentImages = images || []; |
| hideGeneratingUI(); |
| displayImages(currentImages); |
| }; |
| |
| var handleGenerationError = function(error) { |
| resetToInitialState(); |
| showErrorModal(error); |
| }; |
| |
| var handleGenerationCancelled = function() { |
| resetToInitialState(); |
| }; |
| |
| var handleImageDeleted = function(images) { |
| currentImages = images || []; |
| displayImages(currentImages); |
| }; |
| |
| var createImageCard = function(image, index) { |
| var downloadIcon = [ |
| '<path d="M12 16L7 11L8.4 9.55L11 12.15V4H13', |
| 'V12.15L15.6 9.55L17 11L12 16Z" ', |
| 'fill="currentColor"/>', |
| '<path d="M4 20C3.45 20 2.98 19.8 2.59 19.41', |
| 'C2.2 19.02 2 18.55 2 18V15H4V18H20V15H22V18', |
| 'C22 18.55 21.8 19.02 21.41 19.41C21.02 19.8 ', |
| '20.55 20 20 20H4Z" fill="currentColor"/>' |
| ].join(''); |
| |
| var deleteIcon = [ |
| '<path d="M18.3 5.71C17.91 5.32 17.28 5.32 ', |
| '16.89 5.71L12 10.59L7.11 5.7C6.72 5.31 6.09 ', |
| '5.31 5.7 5.7C5.31 6.09 5.31 6.72 5.7 7.11', |
| 'L10.59 12L5.7 16.89C5.31 17.28 5.31 17.91 ', |
| '5.7 18.3C6.09 18.69 6.72 18.69 7.11 18.3L12 ', |
| '13.41L16.89 18.3C17.28 18.69 17.91 18.69 ', |
| '18.3 18.3C18.69 17.91 18.69 17.28 18.3 ', |
| '16.89L13.41 12L18.3 7.11C18.68 6.73 18.68 ', |
| '6.09 18.3 5.71Z" fill="currentColor"/>' |
| ].join(''); |
| |
| return [ |
| '<div class="image-card">', |
| '<img src="data:image/png;base64,', |
| image.base64, |
| '" alt="', image.prompt, '">', |
| '<div class="image-actions">', |
| '<a href="data:image/png;base64,', |
| image.base64, |
| '" download="generated-', image.id, '.png" ', |
| 'class="action-btn">', |
| '<svg class="action-icon" viewBox="0 0 24 24" ', |
| 'fill="none">', |
| downloadIcon, |
| '</svg>', |
| '</a>', |
| '<button type="button" onclick="deleteImage(', |
| index, |
| ')" class="action-btn">', |
| '<svg class="action-icon" viewBox="0 0 24 24" ', |
| 'fill="none">', |
| deleteIcon, |
| '</svg>', |
| '</button>', |
| '</div>', |
| '<div class="image-info">', |
| '<p class="image-prompt">', image.prompt, '</p>', |
| '<p class="image-meta">', |
| '<span class="image-model">', |
| image.model.toUpperCase(), |
| '</span> | ', |
| image.size, |
| '</p>', |
| '</div>', |
| '</div>' |
| ].join(''); |
| }; |
| |
| var displayImages = function(images) { |
| var outputSection = document.querySelector( |
| '.image-output-section' |
| ); |
| |
| if (!outputSection) return; |
| |
| if (!images || images.length === 0) { |
| showPlaceholder(); |
| } else { |
| outputSection.classList.add('has-images'); |
| var html = ['<div class="image-grid">']; |
| |
| images.forEach(function(image, index) { |
| html.push(createImageCard(image, index)); |
| }); |
| |
| html.push('</div>'); |
| outputSection.innerHTML = html.join(''); |
| } |
| }; |
| |
| var showErrorModal = function(error) { |
| var existingModal = document.getElementById( |
| 'errorModal' |
| ); |
| |
| if (existingModal) existingModal.remove(); |
| |
| var modal = document.createElement('div'); |
| modal.id = 'errorModal'; |
| modal.className = 'modal-overlay'; |
| modal.innerHTML = [ |
| '<div class="modal-content ', |
| 'modal-error-content">', |
| '<div class="modal-inner">', |
| '<h3 class="modal-error-title">Error</h3>', |
| '<p class="modal-error-text">', error, '</p>', |
| '<button onclick="closeErrorModal()" ', |
| 'class="btn btn-primary w-full">OK</button>', |
| '</div>', |
| '</div>' |
| ].join(''); |
| |
| document.body.appendChild(modal); |
| }; |
| |
| window.deleteImage = function(index) { |
| var xhr = new XMLHttpRequest(); |
| xhr.open('POST', '/', true); |
| xhr.setRequestHeader( |
| 'Content-Type', |
| 'application/json' |
| ); |
| |
| xhr.onload = function() { |
| try { |
| var response = JSON.parse(xhr.responseText); |
| if (!response.success && response.error) { |
| showErrorModal(response.error); |
| } |
| } catch (e) {} |
| }; |
| |
| xhr.send(JSON.stringify({ |
| action: 'delete', |
| sessionId: sessionId, |
| imageIndex: index |
| })); |
| }; |
| |
| var initializeImages = function() { |
| var outputSection = document.querySelector( |
| '.image-output-section' |
| ); |
| |
| if (!outputSection || |
| !outputSection.classList.contains('has-images')) { |
| return; |
| } |
| |
| var imageCards = outputSection.querySelectorAll( |
| '.image-card' |
| ); |
| |
| if (!imageCards || imageCards.length === 0) return; |
| |
| currentImages = []; |
| |
| imageCards.forEach(function(card) { |
| var img = card.querySelector('img'); |
| var prompt = card.querySelector('.image-prompt'); |
| var model = card.querySelector('.image-model'); |
| var meta = card.querySelector('.image-meta'); |
| |
| if (img && img.src && img.src.includes('base64,')) { |
| var base64 = img.src.split('base64,')[1]; |
| var size = meta |
| ? meta.textContent.split('|')[1] |
| : ''; |
| |
| currentImages.push({ |
| id: 'existing-' + Math.random() |
| .toString(36).substring(2, 15), |
| base64: base64, |
| prompt: prompt ? prompt.textContent : '', |
| model: model |
| ? model.textContent.toLowerCase() |
| : '', |
| size: size ? size.trim() : '' |
| }); |
| } |
| }); |
| }; |
| |
| connectWebSocket(); |
| initializeImages(); |
| })(); |