|
|
|
|
|
|
|
|
|
|
|
(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(); |
|
})(); |