Spaces:
Runtime error
Runtime error
| <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> | |