Spaces:
Running
Running
| // | |
| // SPDX-FileCopyrightText: Hadad <[email protected]> | |
| // SPDX-License-Identifier: Apache-2.0 | |
| // | |
| (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(); | |
| })(); |