BoxOfColors Claude Sonnet 4.6 commited on
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>

Files changed (1) hide show
  1. app.py +75 -25
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
- function _listenAndApply(eventId, slot_id, seg_idx) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- if (lbl) lbl.textContent = msg.output && msg.output.error ?
2178
- 'Error regenerating' : 'Done';
2179
- var hadError = !!(msg.output && msg.output.error);
2180
- console.log('[fireRegen] completed for', slot_id, 'error:', hadError, msg.output && msg.output.error || '');
2181
-
2182
- // Apply video src AFTER Gradio/Svelte finishes its own re-render cycle.
2183
- // We try at 50ms, 300ms, and 800ms to catch whenever Svelte settles.
2184
- var src = _pendingVideoSrc;
2185
- if (src) {
2186
- _applyVideoSrc(slot_id, src);
2187
- setTimeout(function() { _applyVideoSrc(slot_id, src); }, 50);
2188
- setTimeout(function() { _applyVideoSrc(slot_id, src); }, 300);
2189
- setTimeout(function() { _applyVideoSrc(slot_id, src); }, 800);
2190
- // Also install a MutationObserver for 2s to re-apply if Svelte resets src
2191
- var vidEl = document.getElementById('slot_vid_' + slot_id);
2192
- if (vidEl) {
2193
- var obs = new MutationObserver(function() {
2194
- _applyVideoSrc(slot_id, src);
2195
- });
2196
- obs.observe(vidEl, {subtree: true, attributes: true, attributeFilter: ['src'], childList: true});
2197
- setTimeout(function() { obs.disconnect(); }, 2000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
  }