Spaces:
Running on Zero
Running on Zero
Commit ·
ff59c3d
1
Parent(s): d8fbb30
feat: show quota/error toast and restore UI state on failed regen
Browse files- Snapshot waveform HTML and video src before firing regen
- On process_completed with error (e.g. ZeroGPU quota exceeded):
- Show red toast with full error message including countdown timer
- Restore waveform to pre-regen HTML
- Restore video src to pre-regen value
- Set segment label to 'Regen failed — quota exceeded'
- Add _showRegenToast() helper styled like ZeroGPU warnings
- Remove debug SSE data[0]/data[1] console.log
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
app.py
CHANGED
|
@@ -2078,6 +2078,15 @@ _GLOBAL_JS = """
|
|
| 2078 |
|
| 2079 |
console.log('[fireRegen] calling api', apiName, 'fn_index', fnIndex, 'seg', seg_idx);
|
| 2080 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2081 |
// Show spinner immediately
|
| 2082 |
const lbl = document.getElementById('wf_seglabel_' + slot_id);
|
| 2083 |
if (lbl) lbl.textContent = 'Regenerating Seg ' + (seg_idx + 1) + '...';
|
|
@@ -2096,10 +2105,11 @@ _GLOBAL_JS = """
|
|
| 2096 |
if (!j.event_id) { console.error('[fireRegen] no event_id:', j); return; }
|
| 2097 |
console.log('[fireRegen] queued, event_id:', j.event_id);
|
| 2098 |
// Subscribe to SSE stream and apply outputs when ready
|
| 2099 |
-
_listenAndApply(j.event_id, slot_id, seg_idx);
|
| 2100 |
}).catch(function(e) {
|
| 2101 |
console.error('[fireRegen] fetch error:', e);
|
| 2102 |
if (lbl) lbl.textContent = 'Error — see console';
|
|
|
|
| 2103 |
});
|
| 2104 |
}
|
| 2105 |
|
|
@@ -2118,7 +2128,24 @@ _GLOBAL_JS = """
|
|
| 2118 |
return true;
|
| 2119 |
}
|
| 2120 |
|
| 2121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2122 |
var _pendingVideoSrc = null; // track the latest resolved video URL
|
| 2123 |
const es = new EventSource('/gradio_api/queue/data?session_hash=' + window.__gradio_session_hash__);
|
| 2124 |
es.onmessage = function(e) {
|
|
@@ -2128,8 +2155,6 @@ _GLOBAL_JS = """
|
|
| 2128 |
if (msg.msg === 'process_generating' || msg.msg === 'process_completed') {
|
| 2129 |
var out = msg.output;
|
| 2130 |
if (out && out.data) {
|
| 2131 |
-
console.log('[fireRegen] SSE data[0]:', JSON.stringify(out.data[0]).slice(0,200),
|
| 2132 |
-
' data[1]:', JSON.stringify(out.data[1]).slice(0,80));
|
| 2133 |
// data[0] = video update, data[1] = waveform HTML update
|
| 2134 |
var vidUpdate = out.data[0];
|
| 2135 |
var waveUpdate = out.data[1];
|
|
@@ -2173,28 +2198,53 @@ _GLOBAL_JS = """
|
|
| 2173 |
|
| 2174 |
if (msg.msg === 'process_completed') {
|
| 2175 |
es.close();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2176 |
var lbl = document.getElementById('wf_seglabel_' + slot_id);
|
| 2177 |
-
|
| 2178 |
-
|
| 2179 |
-
|
| 2180 |
-
|
| 2181 |
-
|
| 2182 |
-
|
| 2183 |
-
|
| 2184 |
-
|
| 2185 |
-
|
| 2186 |
-
|
| 2187 |
-
|
| 2188 |
-
|
| 2189 |
-
|
| 2190 |
-
|
| 2191 |
-
|
| 2192 |
-
|
| 2193 |
-
|
| 2194 |
-
|
| 2195 |
-
}
|
| 2196 |
-
|
| 2197 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2198 |
}
|
| 2199 |
}
|
| 2200 |
}
|
|
|
|
| 2078 |
|
| 2079 |
console.log('[fireRegen] calling api', apiName, 'fn_index', fnIndex, 'seg', seg_idx);
|
| 2080 |
|
| 2081 |
+
// Snapshot current waveform HTML + video src before mutating anything,
|
| 2082 |
+
// so we can restore on error (e.g. quota exceeded).
|
| 2083 |
+
var _preRegenWaveHtml = null;
|
| 2084 |
+
var _preRegenVideoSrc = null;
|
| 2085 |
+
var waveElSnap = document.getElementById('slot_wave_' + slot_id);
|
| 2086 |
+
if (waveElSnap) _preRegenWaveHtml = waveElSnap.innerHTML;
|
| 2087 |
+
var vidElSnap = document.getElementById('slot_vid_' + slot_id);
|
| 2088 |
+
if (vidElSnap) { var vSnap = vidElSnap.querySelector('video'); if (vSnap) _preRegenVideoSrc = vSnap.getAttribute('src'); }
|
| 2089 |
+
|
| 2090 |
// Show spinner immediately
|
| 2091 |
const lbl = document.getElementById('wf_seglabel_' + slot_id);
|
| 2092 |
if (lbl) lbl.textContent = 'Regenerating Seg ' + (seg_idx + 1) + '...';
|
|
|
|
| 2105 |
if (!j.event_id) { console.error('[fireRegen] no event_id:', j); return; }
|
| 2106 |
console.log('[fireRegen] queued, event_id:', j.event_id);
|
| 2107 |
// Subscribe to SSE stream and apply outputs when ready
|
| 2108 |
+
_listenAndApply(j.event_id, slot_id, seg_idx, _preRegenWaveHtml, _preRegenVideoSrc);
|
| 2109 |
}).catch(function(e) {
|
| 2110 |
console.error('[fireRegen] fetch error:', e);
|
| 2111 |
if (lbl) lbl.textContent = 'Error — see console';
|
| 2112 |
+
_showRegenToast('Request failed: ' + e.message, true);
|
| 2113 |
});
|
| 2114 |
}
|
| 2115 |
|
|
|
|
| 2128 |
return true;
|
| 2129 |
}
|
| 2130 |
|
| 2131 |
+
// Toast notification — styled like ZeroGPU quota warnings.
|
| 2132 |
+
function _showRegenToast(message, isError) {
|
| 2133 |
+
var t = document.createElement('div');
|
| 2134 |
+
t.style.cssText = 'position:fixed;bottom:24px;left:50%;transform:translateX(-50%);' +
|
| 2135 |
+
'z-index:999999;padding:12px 20px;border-radius:8px;font-family:sans-serif;' +
|
| 2136 |
+
'font-size:13px;max-width:480px;text-align:center;box-shadow:0 4px 20px rgba(0,0,0,.5);' +
|
| 2137 |
+
'background:' + (isError ? '#7a1c1c' : '#1c4a1c') + ';color:#fff;' +
|
| 2138 |
+
'border:1px solid ' + (isError ? '#c0392b' : '#27ae60') + ';';
|
| 2139 |
+
t.textContent = message;
|
| 2140 |
+
document.body.appendChild(t);
|
| 2141 |
+
setTimeout(function() {
|
| 2142 |
+
t.style.transition = 'opacity 0.5s';
|
| 2143 |
+
t.style.opacity = '0';
|
| 2144 |
+
setTimeout(function() { t.parentNode && t.parentNode.removeChild(t); }, 600);
|
| 2145 |
+
}, isError ? 8000 : 3000);
|
| 2146 |
+
}
|
| 2147 |
+
|
| 2148 |
+
function _listenAndApply(eventId, slot_id, seg_idx, preRegenWaveHtml, preRegenVideoSrc) {
|
| 2149 |
var _pendingVideoSrc = null; // track the latest resolved video URL
|
| 2150 |
const es = new EventSource('/gradio_api/queue/data?session_hash=' + window.__gradio_session_hash__);
|
| 2151 |
es.onmessage = function(e) {
|
|
|
|
| 2155 |
if (msg.msg === 'process_generating' || msg.msg === 'process_completed') {
|
| 2156 |
var out = msg.output;
|
| 2157 |
if (out && out.data) {
|
|
|
|
|
|
|
| 2158 |
// data[0] = video update, data[1] = waveform HTML update
|
| 2159 |
var vidUpdate = out.data[0];
|
| 2160 |
var waveUpdate = out.data[1];
|
|
|
|
| 2198 |
|
| 2199 |
if (msg.msg === 'process_completed') {
|
| 2200 |
es.close();
|
| 2201 |
+
var errMsg = msg.output && msg.output.error;
|
| 2202 |
+
var hadError = !!errMsg;
|
| 2203 |
+
console.log('[fireRegen] completed for', slot_id, 'error:', hadError, errMsg || '');
|
| 2204 |
+
|
| 2205 |
var lbl = document.getElementById('wf_seglabel_' + slot_id);
|
| 2206 |
+
|
| 2207 |
+
if (hadError) {
|
| 2208 |
+
// Show toast with the full error message (includes quota countdown)
|
| 2209 |
+
var toastMsg = typeof errMsg === 'string' ? errMsg : JSON.stringify(errMsg);
|
| 2210 |
+
_showRegenToast('⚠ ' + toastMsg, true);
|
| 2211 |
+
|
| 2212 |
+
// Restore waveform HTML to pre-regen snapshot
|
| 2213 |
+
if (preRegenWaveHtml !== null) {
|
| 2214 |
+
var waveEl = document.getElementById('slot_wave_' + slot_id);
|
| 2215 |
+
if (waveEl) waveEl.innerHTML = preRegenWaveHtml;
|
| 2216 |
+
}
|
| 2217 |
+
|
| 2218 |
+
// Restore video src to pre-regen snapshot
|
| 2219 |
+
if (preRegenVideoSrc !== null) {
|
| 2220 |
+
var vidElR = document.getElementById('slot_vid_' + slot_id);
|
| 2221 |
+
if (vidElR) {
|
| 2222 |
+
var vR = vidElR.querySelector('video');
|
| 2223 |
+
if (vR) { vR.setAttribute('src', preRegenVideoSrc); vR.src = preRegenVideoSrc; vR.load(); }
|
| 2224 |
+
}
|
| 2225 |
+
}
|
| 2226 |
+
|
| 2227 |
+
if (lbl) lbl.textContent = 'Regen failed — quota exceeded';
|
| 2228 |
+
} else {
|
| 2229 |
+
if (lbl) lbl.textContent = 'Done';
|
| 2230 |
+
|
| 2231 |
+
// Apply video src AFTER Gradio/Svelte finishes its own re-render cycle.
|
| 2232 |
+
// We try at 50ms, 300ms, and 800ms to catch whenever Svelte settles.
|
| 2233 |
+
var src = _pendingVideoSrc;
|
| 2234 |
+
if (src) {
|
| 2235 |
+
_applyVideoSrc(slot_id, src);
|
| 2236 |
+
setTimeout(function() { _applyVideoSrc(slot_id, src); }, 50);
|
| 2237 |
+
setTimeout(function() { _applyVideoSrc(slot_id, src); }, 300);
|
| 2238 |
+
setTimeout(function() { _applyVideoSrc(slot_id, src); }, 800);
|
| 2239 |
+
// Also install a MutationObserver for 2s to re-apply if Svelte resets src
|
| 2240 |
+
var vidEl = document.getElementById('slot_vid_' + slot_id);
|
| 2241 |
+
if (vidEl) {
|
| 2242 |
+
var obs = new MutationObserver(function() {
|
| 2243 |
+
_applyVideoSrc(slot_id, src);
|
| 2244 |
+
});
|
| 2245 |
+
obs.observe(vidEl, {subtree: true, attributes: true, attributeFilter: ['src'], childList: true});
|
| 2246 |
+
setTimeout(function() { obs.disconnect(); }, 2000);
|
| 2247 |
+
}
|
| 2248 |
}
|
| 2249 |
}
|
| 2250 |
}
|