CherithCutestory commited on
Commit
5d03a44
·
1 Parent(s): 2e0989d

Added cache stat tracking endpoint

Browse files
Files changed (2) hide show
  1. app.py +19 -0
  2. index.html +65 -0
app.py CHANGED
@@ -134,6 +134,8 @@ CANONICAL_EMOTIONS = [
134
 
135
  tts_model = None
136
  _voice_cond_cache: LRUCache = LRUCache(maxsize=VOICE_COND_CACHE_MAXSIZE)
 
 
137
 
138
 
139
  def load_model():
@@ -408,13 +410,16 @@ async def convert_text_to_speech(request: Request):
408
  "error_code": "INVALID_REQUEST"
409
  })
410
 
 
411
  cache_key = hashlib.sha256(wav_bytes).hexdigest()
412
  cached_conds = _voice_cond_cache.get(cache_key)
413
 
414
  if cached_conds is not None:
 
415
  logger.info(f"Voice conditioning cache hit ({cache_key[:8]}...), skipping prepare_conditionals")
416
  tts_model.conds = cached_conds
417
  else:
 
418
  tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
419
  tmp.write(wav_bytes)
420
  tmp.close()
@@ -520,6 +525,20 @@ async def convert_text_to_speech(request: Request):
520
  pass
521
 
522
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
523
  @app.get("/", response_class=HTMLResponse)
524
  async def root():
525
  html_path = Path(__file__).parent / "index.html"
 
134
 
135
  tts_model = None
136
  _voice_cond_cache: LRUCache = LRUCache(maxsize=VOICE_COND_CACHE_MAXSIZE)
137
+ _cache_hits: int = 0
138
+ _cache_misses: int = 0
139
 
140
 
141
  def load_model():
 
410
  "error_code": "INVALID_REQUEST"
411
  })
412
 
413
+ global _cache_hits, _cache_misses
414
  cache_key = hashlib.sha256(wav_bytes).hexdigest()
415
  cached_conds = _voice_cond_cache.get(cache_key)
416
 
417
  if cached_conds is not None:
418
+ _cache_hits += 1
419
  logger.info(f"Voice conditioning cache hit ({cache_key[:8]}...), skipping prepare_conditionals")
420
  tts_model.conds = cached_conds
421
  else:
422
+ _cache_misses += 1
423
  tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
424
  tmp.write(wav_bytes)
425
  tmp.close()
 
525
  pass
526
 
527
 
528
+ @app.get("/cache-stats")
529
+ async def cache_stats(request: Request):
530
+ verify_auth(request)
531
+ total = _cache_hits + _cache_misses
532
+ return {
533
+ "cache_size": len(_voice_cond_cache),
534
+ "cache_maxsize": VOICE_COND_CACHE_MAXSIZE,
535
+ "cache_keys": [k[:8] + "..." for k in _voice_cond_cache.keys()],
536
+ "cache_hits": _cache_hits,
537
+ "cache_misses": _cache_misses,
538
+ "hit_rate": round(_cache_hits / total, 3) if total > 0 else None,
539
+ }
540
+
541
+
542
  @app.get("/", response_class=HTMLResponse)
543
  async def root():
544
  html_path = Path(__file__).parent / "index.html"
index.html CHANGED
@@ -164,6 +164,37 @@
164
  .health-badge.ok { background: rgba(34,197,94,0.15); color: #22c55e; }
165
  .health-badge.error { background: rgba(239,68,68,0.15); color: #ef4444; }
166
  .health-badge.loading { background: rgba(167,139,250,0.15); color: #a78bfa; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  .header-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.25rem; }
168
  .emotion-grid {
169
  display: grid;
@@ -425,6 +456,8 @@
425
  resultContent.innerHTML =
426
  '<audio controls autoplay src="' + url + '"></audio>' +
427
  '<a class="download-link" href="' + url + '" download="chatterbox_output.wav">Download WAV</a>';
 
 
428
  } catch (e) {
429
  resultContent.innerHTML = '<div class="error-box">Request failed: ' + e.message + "</div>";
430
  } finally {
@@ -433,6 +466,38 @@
433
  }
434
  }
435
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
  async function checkHealth() {
437
  const badge = document.getElementById("healthBadge");
438
  try {
 
164
  .health-badge.ok { background: rgba(34,197,94,0.15); color: #22c55e; }
165
  .health-badge.error { background: rgba(239,68,68,0.15); color: #ef4444; }
166
  .health-badge.loading { background: rgba(167,139,250,0.15); color: #a78bfa; }
167
+ .cache-stats {
168
+ margin-top: 1rem;
169
+ padding: 0.85rem 1rem;
170
+ background: #12101e;
171
+ border: 1px solid #2d2a3a;
172
+ border-radius: 8px;
173
+ font-size: 0.8rem;
174
+ }
175
+ .cache-stats-title {
176
+ font-size: 0.7rem;
177
+ font-weight: 600;
178
+ text-transform: uppercase;
179
+ letter-spacing: 0.05em;
180
+ color: #a78bfa;
181
+ margin-bottom: 0.6rem;
182
+ }
183
+ .cache-stat-row { display: flex; justify-content: space-between; margin-bottom: 0.3rem; color: #b0adc0; }
184
+ .cache-stat-row:last-child { margin-bottom: 0; }
185
+ .cache-stat-val { font-weight: 600; color: #e2e0eb; }
186
+ .cache-keys { margin-top: 0.5rem; }
187
+ .cache-key-chip {
188
+ display: inline-block;
189
+ padding: 0.15rem 0.5rem;
190
+ margin: 0.2rem 0.2rem 0 0;
191
+ background: rgba(124,58,237,0.15);
192
+ border: 1px solid rgba(124,58,237,0.3);
193
+ border-radius: 4px;
194
+ font-family: monospace;
195
+ font-size: 0.75rem;
196
+ color: #a78bfa;
197
+ }
198
  .header-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.25rem; }
199
  .emotion-grid {
200
  display: grid;
 
456
  resultContent.innerHTML =
457
  '<audio controls autoplay src="' + url + '"></audio>' +
458
  '<a class="download-link" href="' + url + '" download="chatterbox_output.wav">Download WAV</a>';
459
+
460
+ await refreshCacheStats(resultContent);
461
  } catch (e) {
462
  resultContent.innerHTML = '<div class="error-box">Request failed: ' + e.message + "</div>";
463
  } finally {
 
466
  }
467
  }
468
 
469
+ async function refreshCacheStats(container) {
470
+ try {
471
+ const hdrs = {};
472
+ const apiKey = document.getElementById("apiKey").value.trim();
473
+ if (apiKey) hdrs["Authorization"] = "Bearer " + apiKey;
474
+ const resp = await fetch("/cache-stats", { headers: hdrs });
475
+ if (!resp.ok) return;
476
+ const s = await resp.json();
477
+
478
+ const total = s.cache_hits + s.cache_misses;
479
+ const hitPct = total > 0 ? (s.hit_rate * 100).toFixed(1) + "%" : "—";
480
+
481
+ const keysHtml = s.cache_keys.length
482
+ ? s.cache_keys.map(k => '<span class="cache-key-chip">' + k + '</span>').join("")
483
+ : '<span style="color:#9490a8">empty</span>';
484
+
485
+ const statsHtml =
486
+ '<div class="cache-stats">' +
487
+ '<div class="cache-stats-title">Voice Conditioning Cache</div>' +
488
+ '<div class="cache-stat-row"><span>Size</span><span class="cache-stat-val">' + s.cache_size + ' / ' + s.cache_maxsize + '</span></div>' +
489
+ '<div class="cache-stat-row"><span>Hits</span><span class="cache-stat-val">' + s.cache_hits + '</span></div>' +
490
+ '<div class="cache-stat-row"><span>Misses</span><span class="cache-stat-val">' + s.cache_misses + '</span></div>' +
491
+ '<div class="cache-stat-row"><span>Hit rate</span><span class="cache-stat-val">' + hitPct + '</span></div>' +
492
+ '<div class="cache-keys">' + keysHtml + '</div>' +
493
+ '</div>';
494
+
495
+ const existing = container.querySelector(".cache-stats");
496
+ if (existing) existing.outerHTML = statsHtml;
497
+ else container.insertAdjacentHTML("beforeend", statsHtml);
498
+ } catch (_) {}
499
+ }
500
+
501
  async function checkHealth() {
502
  const badge = document.getElementById("healthBadge");
503
  try {