apolinario commited on
Commit
5f5d462
·
1 Parent(s): b7f0f1d

Show ZeroGPU execution-count (runs) quota from new endpoint field

Browse files
Files changed (1) hide show
  1. index.html +63 -20
index.html CHANGED
@@ -1506,6 +1506,7 @@ let resultAudioUrl = null;
1506
  let modalFileData = null;
1507
  let history = JSON.parse(localStorage.getItem("zl_history") || "[]");
1508
  let quotaTimer = null;
 
1509
 
1510
  // ── IndexedDB ──
1511
 
@@ -1704,6 +1705,17 @@ function fmtDuration(sec) {
1704
  return m > 0 ? `${m}m ${s}s` : `${s}s`;
1705
  }
1706
 
 
 
 
 
 
 
 
 
 
 
 
1707
  async function refreshQuota() {
1708
  if (!accessToken) return;
1709
  const tracker = document.getElementById("usage-tracker");
@@ -1713,24 +1725,39 @@ async function refreshQuota() {
1713
  });
1714
  if (!res.ok) throw new Error(`quota ${res.status}`);
1715
  const q = await res.json();
 
 
1716
  const base = q.base || 0;
1717
  const remaining = Math.max(0, q.current || 0);
1718
- const used = Math.max(0, base - remaining);
1719
- const pct = base > 0 ? Math.min((used / base) * 100, 100) : 0;
1720
 
1721
  const fill = document.getElementById("usage-fill");
1722
- fill.style.width = `${pct}%`;
1723
- fill.className = "usage-fill" + (pct > 80 ? " danger" : pct > 50 ? " warning" : "");
1724
-
1725
- document.getElementById("usage-value").textContent = `${fmtDuration(remaining)} left`;
1726
-
1727
- let sub = `${fmtDuration(used)} of ${fmtDuration(base)} ${isPro ? "Pro" : "free"} GPU quota`;
1728
- if (q.overquotaUsed > 0) sub += ` · +${fmtDuration(q.overquotaUsed)} over`;
1729
- if (q.resetsAt) {
1730
- const t = new Date(q.resetsAt);
1731
- if (!isNaN(t)) sub += ` · resets ${t.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1732
  }
1733
- document.getElementById("usage-sub").textContent = sub;
1734
  if (tracker) tracker.style.display = "";
1735
  } catch (err) {
1736
  if (tracker) tracker.style.display = "none";
@@ -1741,17 +1768,29 @@ async function refreshQuota() {
1741
 
1742
  // Build the body of a status bar for an error. The ZeroGPU backend sends a
1743
  // misleading seconds-based message to free users whose real blocker is the
1744
- // daily free-run count (8/day), and wrongly offers paid GPU credits (Pro-only).
1745
- // We reframe by the known account tier (isPro) rather than the backend text.
1746
  function quotaErrorBodyHtml(errMsg) {
1747
  if (!errMsg.includes("GPU quota")) {
1748
  return `<div class="status-text">${errMsg}</div>`;
1749
  }
 
 
 
 
 
 
 
 
 
 
 
1750
  if (!isPro) {
1751
- return `<div class="status-text">You've used all 8 free ZeroGPU runs for today.</div>
1752
  <a class="status-cta upgrade" href="https://huggingface.co/subscribe/pro?from=ZeroGPU" target="_blank">🤗 Subscribe to HF Pro &mdash; 40 min/day of GPU quota</a>
1753
  <div style="font-size:11px;color:var(--text-muted);margin-top:2px">Your free quota resets every 24h</div>`;
1754
  }
 
1755
  let html = `<div class="status-text">${errMsg}</div>
1756
  <a class="status-cta credits" href="https://huggingface.co/settings/billing" target="_blank">Buy additional GPU credits &mdash; $1 per 10 min</a>`;
1757
  const m = errMsg.match(/Try again in ([\d:]+)/);
@@ -1761,10 +1800,11 @@ function quotaErrorBodyHtml(errMsg) {
1761
  return html;
1762
  }
1763
 
1764
- function showStatusWithCTA(errorMsg) {
1765
  const el = document.getElementById("status");
1766
  el.className = "status-bar visible error";
1767
  el.innerHTML = quotaErrorBodyHtml(errorMsg);
 
1768
  }
1769
 
1770
  function showStatus(type, msg) {
@@ -2149,10 +2189,11 @@ function showDesignStatus(type, msg) {
2149
  el.innerHTML = `<div class="status-text">${msg}</div>`;
2150
  }
2151
 
2152
- function showDesignStatusWithCTA(errMsg) {
2153
  const el = document.getElementById("design-status");
2154
  el.className = "status-bar visible error";
2155
  el.innerHTML = quotaErrorBodyHtml(errMsg);
 
2156
  }
2157
 
2158
  const designAudioEl = document.getElementById("design-audio-player");
@@ -2324,10 +2365,11 @@ function showTranscribeStatus(type, msg) {
2324
  el.innerHTML = `<div class="status-text">${msg}</div>`;
2325
  }
2326
 
2327
- function showTranscribeStatusWithCTA(errMsg) {
2328
  const el = document.getElementById("transcribe-status");
2329
  el.className = "status-bar visible error";
2330
  el.innerHTML = quotaErrorBodyHtml(errMsg);
 
2331
  }
2332
 
2333
  window.copyTranscript = function() {
@@ -2884,10 +2926,11 @@ function showStatusOn(elId, type, msg) {
2884
  el.innerHTML = `<div class="status-text">${msg}</div>`;
2885
  }
2886
 
2887
- function showStatusCtaOn(elId, errMsg) {
2888
  const el = document.getElementById(elId);
2889
  el.className = "status-bar visible error";
2890
  el.innerHTML = quotaErrorBodyHtml(errMsg);
 
2891
  }
2892
 
2893
  function wireUploadArea(areaId, inputId, labelId, onFile) {
 
1506
  let modalFileData = null;
1507
  let history = JSON.parse(localStorage.getItem("zl_history") || "[]");
1508
  let quotaTimer = null;
1509
+ let lastQuota = null;
1510
 
1511
  // ── IndexedDB ──
1512
 
 
1705
  return m > 0 ? `${m}m ${s}s` : `${s}s`;
1706
  }
1707
 
1708
+ function fmtResetsIn(iso) {
1709
+ if (!iso) return null;
1710
+ const ms = new Date(iso).getTime() - Date.now();
1711
+ if (isNaN(ms) || ms <= 0) return null;
1712
+ const mins = Math.round(ms / 60000);
1713
+ if (mins < 60) return `${mins}m`;
1714
+ const h = Math.floor(mins / 60);
1715
+ const m = mins % 60;
1716
+ return m > 0 ? `${h}h ${m}m` : `${h}h`;
1717
+ }
1718
+
1719
  async function refreshQuota() {
1720
  if (!accessToken) return;
1721
  const tracker = document.getElementById("usage-tracker");
 
1725
  });
1726
  if (!res.ok) throw new Error(`quota ${res.status}`);
1727
  const q = await res.json();
1728
+ lastQuota = q;
1729
+
1730
  const base = q.base || 0;
1731
  const remaining = Math.max(0, q.current || 0);
1732
+ const secUsed = Math.max(0, base - remaining);
1733
+ const runs = q.runs;
1734
 
1735
  const fill = document.getElementById("usage-fill");
1736
+ const value = document.getElementById("usage-value");
1737
+ const sub = document.getElementById("usage-sub");
1738
+
1739
+ if (runs && runs.limit > 0) {
1740
+ // Free tier: the daily execution count is the binding limit — headline it.
1741
+ const pct = Math.min((runs.used / runs.limit) * 100, 100);
1742
+ fill.style.width = `${pct}%`;
1743
+ fill.className = "usage-fill" + (pct >= 100 ? " danger" : pct > 60 ? " warning" : "");
1744
+ value.textContent = `${Math.round(runs.used)}/${Math.round(runs.limit)} runs`;
1745
+ let line = `${Math.max(0, Math.round(runs.remaining))} free runs left · ${fmtDuration(remaining)} GPU`;
1746
+ const resetIn = fmtResetsIn(runs.resetsAt);
1747
+ if (resetIn) line += ` · resets in ${resetIn}`;
1748
+ sub.textContent = line;
1749
+ } else {
1750
+ // Pro / paid: GPU-seconds are the only limit.
1751
+ const pct = base > 0 ? Math.min((secUsed / base) * 100, 100) : 0;
1752
+ fill.style.width = `${pct}%`;
1753
+ fill.className = "usage-fill" + (pct > 80 ? " danger" : pct > 50 ? " warning" : "");
1754
+ value.textContent = `${fmtDuration(remaining)} left`;
1755
+ let line = `${fmtDuration(secUsed)} of ${fmtDuration(base)} ${isPro ? "Pro" : ""} GPU quota`.replace(/\s+/g, " ");
1756
+ if (q.overquotaUsed > 0) line += ` · +${fmtDuration(q.overquotaUsed)} over`;
1757
+ const resetIn = fmtResetsIn(q.resetsAt);
1758
+ if (resetIn) line += ` · resets in ${resetIn}`;
1759
+ sub.textContent = line;
1760
  }
 
1761
  if (tracker) tracker.style.display = "";
1762
  } catch (err) {
1763
  if (tracker) tracker.style.display = "none";
 
1768
 
1769
  // Build the body of a status bar for an error. The ZeroGPU backend sends a
1770
  // misleading seconds-based message to free users whose real blocker is the
1771
+ // daily execution count, and wrongly offers paid GPU credits (Pro-only).
1772
+ // We reframe using the real quota data (lastQuota.runs) and account tier.
1773
  function quotaErrorBodyHtml(errMsg) {
1774
  if (!errMsg.includes("GPU quota")) {
1775
  return `<div class="status-text">${errMsg}</div>`;
1776
  }
1777
+ const runs = lastQuota && lastQuota.runs;
1778
+ // Free tier hit the daily execution limit (runs present and exhausted).
1779
+ if (runs && runs.limit > 0 && runs.remaining <= 0) {
1780
+ const limit = Math.round(runs.limit);
1781
+ const resetIn = fmtResetsIn(runs.resetsAt);
1782
+ let html = `<div class="status-text">You've used all ${limit} free ZeroGPU runs for today.</div>
1783
+ <a class="status-cta upgrade" href="https://huggingface.co/subscribe/pro?from=ZeroGPU" target="_blank">🤗 Subscribe to HF Pro &mdash; 40 min/day of GPU quota</a>`;
1784
+ if (resetIn) html += `<div style="font-size:11px;color:var(--text-muted);margin-top:2px">Your free runs reset in ${resetIn}</div>`;
1785
+ return html;
1786
+ }
1787
+ // Free tier with runs left, or unknown tier: ran out of GPU minutes.
1788
  if (!isPro) {
1789
+ return `<div class="status-text">You've used your free daily ZeroGPU minutes.</div>
1790
  <a class="status-cta upgrade" href="https://huggingface.co/subscribe/pro?from=ZeroGPU" target="_blank">🤗 Subscribe to HF Pro &mdash; 40 min/day of GPU quota</a>
1791
  <div style="font-size:11px;color:var(--text-muted);margin-top:2px">Your free quota resets every 24h</div>`;
1792
  }
1793
+ // Pro / paid: real seconds message + credits CTA (credits are Pro-only).
1794
  let html = `<div class="status-text">${errMsg}</div>
1795
  <a class="status-cta credits" href="https://huggingface.co/settings/billing" target="_blank">Buy additional GPU credits &mdash; $1 per 10 min</a>`;
1796
  const m = errMsg.match(/Try again in ([\d:]+)/);
 
1800
  return html;
1801
  }
1802
 
1803
+ async function showStatusWithCTA(errorMsg) {
1804
  const el = document.getElementById("status");
1805
  el.className = "status-bar visible error";
1806
  el.innerHTML = quotaErrorBodyHtml(errorMsg);
1807
+ if (errorMsg.includes("GPU quota")) { await refreshQuota(); el.innerHTML = quotaErrorBodyHtml(errorMsg); }
1808
  }
1809
 
1810
  function showStatus(type, msg) {
 
2189
  el.innerHTML = `<div class="status-text">${msg}</div>`;
2190
  }
2191
 
2192
+ async function showDesignStatusWithCTA(errMsg) {
2193
  const el = document.getElementById("design-status");
2194
  el.className = "status-bar visible error";
2195
  el.innerHTML = quotaErrorBodyHtml(errMsg);
2196
+ if (errMsg.includes("GPU quota")) { await refreshQuota(); el.innerHTML = quotaErrorBodyHtml(errMsg); }
2197
  }
2198
 
2199
  const designAudioEl = document.getElementById("design-audio-player");
 
2365
  el.innerHTML = `<div class="status-text">${msg}</div>`;
2366
  }
2367
 
2368
+ async function showTranscribeStatusWithCTA(errMsg) {
2369
  const el = document.getElementById("transcribe-status");
2370
  el.className = "status-bar visible error";
2371
  el.innerHTML = quotaErrorBodyHtml(errMsg);
2372
+ if (errMsg.includes("GPU quota")) { await refreshQuota(); el.innerHTML = quotaErrorBodyHtml(errMsg); }
2373
  }
2374
 
2375
  window.copyTranscript = function() {
 
2926
  el.innerHTML = `<div class="status-text">${msg}</div>`;
2927
  }
2928
 
2929
+ async function showStatusCtaOn(elId, errMsg) {
2930
  const el = document.getElementById(elId);
2931
  el.className = "status-bar visible error";
2932
  el.innerHTML = quotaErrorBodyHtml(errMsg);
2933
+ if (errMsg.includes("GPU quota")) { await refreshQuota(); el.innerHTML = quotaErrorBodyHtml(errMsg); }
2934
  }
2935
 
2936
  function wireUploadArea(areaId, inputId, labelId, onFile) {