golem-flask-backend / image_progress_example.html
mememechez's picture
Deploy final cleaned source code
ca28016
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Real-Time Image Generation Progress</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: #1a1a1a;
color: #fff;
}
.progress-container {
background: #2a2a2a;
border-radius: 10px;
padding: 20px;
margin: 20px 0;
border: 1px solid #444;
}
.progress-bar {
width: 100%;
height: 20px;
background: #333;
border-radius: 10px;
overflow: hidden;
margin: 10px 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #00ff88, #00cc66);
width: 0%;
transition: width 0.3s ease;
border-radius: 10px;
}
.progress-text {
display: flex;
justify-content: space-between;
margin: 10px 0;
font-size: 14px;
}
.status {
font-weight: bold;
color: #00ff88;
}
.time-info {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
margin: 15px 0;
}
.time-box {
background: #333;
padding: 10px;
border-radius: 5px;
text-align: center;
}
.time-label {
font-size: 12px;
color: #aaa;
margin-bottom: 5px;
}
.time-value {
font-size: 18px;
font-weight: bold;
color: #00ff88;
}
button {
background: #00ff88;
color: #000;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-weight: bold;
}
button:hover {
background: #00cc66;
}
button:disabled {
background: #666;
cursor: not-allowed;
}
.log {
background: #111;
border: 1px solid #333;
border-radius: 5px;
padding: 10px;
height: 200px;
overflow-y: auto;
font-family: monospace;
font-size: 12px;
margin-top: 20px;
}
.log-entry {
margin: 2px 0;
padding: 2px 0;
}
.log-info { color: #00ff88; }
.log-warning { color: #ffaa00; }
.log-error { color: #ff4444; }
</style>
</head>
<body>
<h1>🎨 Real-Time Image Generation Progress</h1>
<div class="progress-container">
<h3>Image Generation Status</h3>
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="progress-text">
<span class="status" id="statusText">Ready to generate</span>
<span id="percentText">0%</span>
</div>
<div class="time-info">
<div class="time-box">
<div class="time-label">Elapsed</div>
<div class="time-value" id="elapsedTime">0.0s</div>
</div>
<div class="time-box">
<div class="time-label">Remaining</div>
<div class="time-value" id="remainingTime">--</div>
</div>
<div class="time-box">
<div class="time-label">Total Est.</div>
<div class="time-value" id="totalTime">--</div>
</div>
</div>
<div style="margin-top: 20px;">
<input type="text" id="promptInput" placeholder="Enter your image prompt..."
style="width: 70%; padding: 10px; margin-right: 10px; background: #333; color: #fff; border: 1px solid #555; border-radius: 5px;">
<button onclick="startGeneration()" id="generateBtn">Generate Image</button>
</div>
</div>
<div class="log" id="logContainer">
<div class="log-entry log-info">Ready to start image generation...</div>
</div>
<script>
let currentRequestId = null;
let eventSource = null;
let pollingInterval = null;
function log(message, type = 'info') {
const logContainer = document.getElementById('logContainer');
const entry = document.createElement('div');
entry.className = `log-entry log-${type}`;
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
logContainer.appendChild(entry);
logContainer.scrollTop = logContainer.scrollHeight;
}
function updateProgress(data) {
const progressFill = document.getElementById('progressFill');
const statusText = document.getElementById('statusText');
const percentText = document.getElementById('percentText');
const elapsedTime = document.getElementById('elapsedTime');
const remainingTime = document.getElementById('remainingTime');
const totalTime = document.getElementById('totalTime');
progressFill.style.width = `${data.percent || 0}%`;
statusText.textContent = data.status || 'Unknown';
percentText.textContent = `${data.percent || 0}%`;
elapsedTime.textContent = data.elapsed_formatted || '0.0s';
remainingTime.textContent = data.remaining_formatted || '--';
totalTime.textContent = data.total_estimated || '--';
log(`Progress: ${data.percent || 0}% - ${data.status || 'Unknown'} - Step ${data.step || 0}/${data.total || 100}`);
}
function startGeneration() {
const prompt = document.getElementById('promptInput').value.trim();
if (!prompt) {
alert('Please enter a prompt!');
return;
}
const generateBtn = document.getElementById('generateBtn');
generateBtn.disabled = true;
generateBtn.textContent = 'Generating...';
currentRequestId = 'img_' + Date.now();
log(`Starting image generation for: "${prompt}"`, 'info');
// Start image generation
fetch('/image/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
prompt: prompt,
provider: 'qwen_local',
request_id: currentRequestId,
width: 512,
height: 512,
steps: 20
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
log('Image generation completed successfully!', 'info');
updateProgress({
percent: 100,
status: 'completed',
elapsed_formatted: data.generation_time ? `${data.generation_time.toFixed(1)}s` : 'N/A'
});
} else {
log(`Generation failed: ${data.error}`, 'error');
updateProgress({
percent: 0,
status: 'failed'
});
}
generateBtn.disabled = false;
generateBtn.textContent = 'Generate Image';
stopProgressTracking();
})
.catch(error => {
log(`Network error: ${error.message}`, 'error');
generateBtn.disabled = false;
generateBtn.textContent = 'Generate Image';
stopProgressTracking();
});
// Start progress tracking using Server-Sent Events (preferred)
startProgressTracking();
}
function startProgressTracking() {
if (!currentRequestId) return;
log('Starting real-time progress tracking...', 'info');
// Try Server-Sent Events first (more efficient)
try {
eventSource = new EventSource(`/image/progress/stream?request_id=${currentRequestId}`);
eventSource.onmessage = function(event) {
try {
const data = JSON.parse(event.data);
updateProgress(data);
if (data.status === 'completed' || data.status === 'failed') {
stopProgressTracking();
}
} catch (e) {
log(`Progress parsing error: ${e.message}`, 'warning');
}
};
eventSource.onerror = function(event) {
log('SSE connection error, falling back to polling...', 'warning');
eventSource.close();
eventSource = null;
startPolling();
};
} catch (e) {
log('SSE not supported, using polling...', 'warning');
startPolling();
}
}
function startPolling() {
if (!currentRequestId) return;
pollingInterval = setInterval(() => {
fetch(`/image/progress?request_id=${currentRequestId}`)
.then(response => response.json())
.then(data => {
if (data.success) {
updateProgress(data);
if (data.status === 'completed' || data.status === 'failed') {
stopProgressTracking();
}
}
})
.catch(error => {
log(`Polling error: ${error.message}`, 'warning');
});
}, 1000); // Poll every second
}
function stopProgressTracking() {
if (eventSource) {
eventSource.close();
eventSource = null;
log('Stopped SSE progress tracking', 'info');
}
if (pollingInterval) {
clearInterval(pollingInterval);
pollingInterval = null;
log('Stopped polling progress tracking', 'info');
}
}
// Clean up on page unload
window.addEventListener('beforeunload', stopProgressTracking);
</script>
</body>
</html>