Commit ·
5d03a44
1
Parent(s): 2e0989d
Added cache stat tracking endpoint
Browse files- app.py +19 -0
- 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 {
|