feat: voices table shows only Factor+WPM; Benchmark gets full detail (v1.12.14)
Voices table: removed Length, Duration, Time — only Factor and WPM remain from benchmark data. Setup → Benchmark batch results now shows Duration, Factor (sorted fastest-first, colour-coded), Time, and WPM. Factor replaces Avg RTF with the same data flipped to a more intuitive direction (higher=better). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
90a481f8d8
commit
5db93d55d1
@ -9,6 +9,14 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
||||
|
||||
---
|
||||
|
||||
## [1.12.14] — 2026-06-27
|
||||
|
||||
### Changed
|
||||
- **Voices table**: simplified to only Factor + WPM from benchmark data (Length, Duration, Time removed — detail lives in Setup → Benchmark).
|
||||
- **Setup → Benchmark batch results**: now shows Duration, Factor (sorted descending, green ≥ 1.0×), Time, and WPM alongside existing Latency/Best columns. Factor replaces Avg RTF (same data, more intuitive direction).
|
||||
|
||||
---
|
||||
|
||||
## [1.12.13] — 2026-06-27
|
||||
|
||||
### Changed
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
|
||||
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
|
||||
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css">
|
||||
<link rel="stylesheet" href="/static/style.css?v=1.12.13">
|
||||
<link rel="stylesheet" href="/static/style.css?v=1.12.14">
|
||||
|
||||
|
||||
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
|
||||
@ -336,7 +336,7 @@
|
||||
<script src="/static/vendor/wavesurfer-regions.min.js"></script>
|
||||
|
||||
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
|
||||
<script src="/static/loader.js?v=1.12.13"></script>
|
||||
<script src="/static/loader.js?v=1.12.14"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -225,12 +225,16 @@
|
||||
const sorted = batchResults.slice().sort((a, b) => {
|
||||
if (a.ok && !b.ok) return -1;
|
||||
if (!a.ok && b.ok) return 1;
|
||||
return (a.avgRtf || 999) - (b.avgRtf || 999);
|
||||
// sort by Factor descending (higher = faster); Factor = 1/avgRtf
|
||||
return (b.avgRtf > 0 ? 1/b.avgRtf : 0) - (a.avgRtf > 0 ? 1/a.avgRtf : 0);
|
||||
});
|
||||
const okCount = batchResults.filter(r => r.ok).length;
|
||||
if (batchResultsLabel) batchResultsLabel.textContent = `${okCount} / ${batchResults.length} voices — ${batchBackendSel.value}`;
|
||||
batchTbody.innerHTML = sorted.map(r => {
|
||||
const rtfCls = r.ok && r.avgRtf < 1 ? 'perf-good' : r.ok ? 'perf-slow' : '';
|
||||
const factor = r.ok && r.avgRtf > 0 ? 1 / r.avgRtf : null;
|
||||
const timeSec = r.ok && r.avgLatency > 0 ? r.avgLatency / 1000 : null;
|
||||
const wpm = r.ok && r.avgAudio > 0 && r.wordCount > 0 ? Math.round(r.wordCount / (r.avgAudio / 60)) : null;
|
||||
const factorCls = factor == null ? '' : factor >= 1 ? 'perf-good' : 'perf-slow';
|
||||
const trend = r.trend ? `<span class="perf-trend-badge ${r.trend.cls}" style="font-size:11px;padding:1px 6px">${r.trend.label}</span>` : '';
|
||||
const device = typeof backendComputeDevice === 'function' ? backendComputeDevice(r.backend || batchBackendSel.value) : 'Unknown';
|
||||
const deviceCls = typeof backendComputeDeviceClass === 'function' ? backendComputeDeviceClass(r.backend || batchBackendSel.value) : '';
|
||||
@ -239,8 +243,10 @@
|
||||
<td><span class="bench-device ${deviceCls}" title="Inferred from the selected TTS backend metadata">${escHtml(device)}</span></td>
|
||||
<td>${r.ok ? Math.round(r.avgLatency) + ' ms' : '—'}</td>
|
||||
<td>${r.ok ? r.minLatency + ' ms' : '—'}</td>
|
||||
<td>${r.ok && r.avgAudio > 0 ? r.avgAudio.toFixed(2) : '—'}</td>
|
||||
<td class="${rtfCls}">${r.ok && r.avgRtf ? r.avgRtf.toFixed(2) : '—'}${trend}</td>
|
||||
<td>${r.ok && r.avgAudio > 0 ? r.avgAudio.toFixed(1) + 's' : '—'}</td>
|
||||
<td class="${factorCls}">${factor != null ? factor.toFixed(2) + '×' : '—'}${trend}</td>
|
||||
<td>${timeSec != null ? timeSec.toFixed(1) + 's' : '—'}</td>
|
||||
<td>${wpm != null ? wpm + ' wpm' : '—'}</td>
|
||||
<td>${r.ok ? '<span class="perf-ok">OK</span>' : `<span class="perf-err">${escHtml(r.error || 'Failed')}</span>`}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
@ -281,7 +287,7 @@
|
||||
batchProgCount.textContent = `${vi + 1} / ${selected.length}`;
|
||||
batchProgBar.style.width = `${Math.round((vi / selected.length) * 100)}%`;
|
||||
|
||||
const entry = { voice, backend, ok: false, avgLatency: 0, minLatency: 0, avgRtf: 0, avgAudio: 0, error: '' };
|
||||
const entry = { voice, backend, ok: false, avgLatency: 0, minLatency: 0, avgRtf: 0, avgAudio: 0, wordCount: text ? text.trim().split(/\s+/).length : 0, error: '' };
|
||||
const rowRunResults = [];
|
||||
|
||||
for (let ri = 0; ri < runs; ri++) {
|
||||
|
||||
@ -2018,10 +2018,7 @@ function makeVoiceRow(v) {
|
||||
? `<select class="vl-inline-edit" data-field="gender" style="width:100%"><option value="N" ${gender==='N'?'selected':''}>N</option><option value="M" ${gender==='M'?'selected':''}>M</option><option value="F" ${gender==='F'?'selected':''}>F</option></select>`
|
||||
: `${genderMap[gender]||'?'} ${escHtml(genderLabel[gender]||'—')}`}
|
||||
</div>
|
||||
<div class="vl-tbl-len">${fmtDuration(v.duration)}</div>
|
||||
<div class="vl-tbl-dur" title="${escHtml(voiceBenchmarkAudioSec(v) != null ? `${fmtBenchmarkAudio(v)} — length of synthesised benchmark audio` : 'Benchmark this voice to see output duration')}">${escHtml(fmtBenchmarkAudio(v))}</div>
|
||||
<div class="vl-tbl-factor ${benchCls}" title="${escHtml(benchTitle)}">${escHtml(fmtFactor(v))}</div>
|
||||
<div class="vl-tbl-time ${benchCls}" title="${escHtml(benchTitle)}">${escHtml(fmtElapsed(v))}</div>
|
||||
<div class="vl-tbl-wpm" title="${escHtml(voiceWpm(v) != null ? `${voiceWpm(v)} words per minute — how fast this voice speaks. 130–180 wpm is natural for long listening.` : 'Benchmark this voice to see speaking rate')}">${escHtml(fmtWpm(v))}</div>
|
||||
<div class="vl-tbl-dbfs" title="${dbTitle}">${dbfs} dB</div>
|
||||
<div class="vl-tbl-type"><span class="vl-type-label ${isClone ? 'vl-type-clone' : 'vl-type-design'}">${isClone ? 'Clone' : 'Design'}</span></div>
|
||||
|
||||
@ -183,12 +183,12 @@
|
||||
<!-- Batch results -->
|
||||
<div class="card" id="batch-results-card" style="display:none">
|
||||
<h2>Batch results <span id="batch-results-label" class="s-label-note"></span></h2>
|
||||
<p class="card-subtitle">Sorted fastest RTF first. Green = real-time capable (<1.0). Trend compares to the previous session for each voice.</p>
|
||||
<p class="card-subtitle">Sorted fastest Factor first. Green = faster than real-time (Factor > 1.0). Trend compares to the previous session.</p>
|
||||
<div class="perf-table-wrap">
|
||||
<table class="perf-table" id="batch-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Voice</th><th>Device</th><th>Avg latency</th><th>Best</th><th>Audio (s)</th><th>Avg RTF</th><th>Status</th>
|
||||
<th>Voice</th><th>Device</th><th title="Avg time to first audio chunk (ms)">Latency</th><th title="Best (fastest) run latency">Best</th><th title="Average synthesised audio duration in seconds">Duration</th><th title="audio ÷ render — higher is better; above 1.0× = faster than real-time">Factor</th><th title="Average total render time in seconds">Time</th><th title="Words per minute the voice speaks">WPM</th><th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="batch-tbody"></tbody>
|
||||
|
||||
@ -108,10 +108,7 @@
|
||||
<div class="vl-th-sortable" data-sort="id">Name <span class="mdi mdi-sort"></span></div>
|
||||
<div class="vl-th-sortable" data-sort="flag">Lang <span class="mdi mdi-sort"></span></div>
|
||||
<div class="vl-th-sortable" data-sort="gender">Gender <span class="mdi mdi-sort"></span></div>
|
||||
<div class="vl-th-sortable" data-sort="duration">Length <span class="mdi mdi-sort"></span></div>
|
||||
<div class="vl-th-sortable" data-sort="bench_audio">Duration <span class="mdi mdi-sort"></span><span class="vl-th-info mdi mdi-information-outline" title="Duration of the synthesised benchmark audio — how many seconds of speech the engine produced for the test sentence."></span></div>
|
||||
<div class="vl-th-sortable" data-sort="factor">Factor <span class="mdi mdi-sort"></span><span class="vl-th-info mdi mdi-information-outline" title="Speed multiplier: audio duration ÷ render time. Above 1.0× means the GPU is faster than real-time — higher is better."></span></div>
|
||||
<div class="vl-th-sortable" data-sort="elapsed">Time <span class="mdi mdi-sort"></span><span class="vl-th-info mdi mdi-information-outline" title="Total render time: how many seconds the engine took to produce the benchmark audio from start to finish."></span></div>
|
||||
<div class="vl-th-sortable" data-sort="wpm">WPM <span class="mdi mdi-sort"></span><span class="vl-th-info mdi mdi-information-outline" title="Words per minute the voice speaks. 130–180 wpm is natural for audiobooks; lower = slower, more deliberate pace."></span></div>
|
||||
<div class="vl-th-sortable" data-sort="seed">Seed <span class="mdi mdi-sort"></span></div>
|
||||
<div class="vl-th-sortable" data-sort="dbfs">dBFS <span class="mdi mdi-sort"></span></div>
|
||||
|
||||
@ -1517,6 +1517,7 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
|
||||
/* Table View mode */
|
||||
.vl-tbl-lang, .vl-tbl-gender, .vl-tbl-type, .vl-tbl-bench, .vl-tbl-dbfs, .vl-tbl-len, .vl-tbl-dur, .vl-tbl-factor, .vl-tbl-time, .vl-tbl-wpm, .vl-tbl-rating, .vl-tbl-source, .vl-tbl-seed, .vl-tbl-note, .vl-tbl-tag, .vl-tbl-active { display: none; }
|
||||
/* len, dur, time hidden in table view — full detail lives in Setup → Benchmark */
|
||||
.vl-table-header { display: none; }
|
||||
|
||||
.voices-workbench.table-view .voices-list-pane { width: 100% !important; max-width: 100%; }
|
||||
@ -1524,7 +1525,7 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
|
||||
.voices-workbench.table-view .vl-table-header {
|
||||
display: grid;
|
||||
grid-template-columns: 24px 32px 60px minmax(140px, 1.2fr) 55px 65px 50px 55px 55px 55px 55px 50px 65px 55px 85px 65px minmax(80px, 1fr) minmax(120px, 1fr) 50px;
|
||||
grid-template-columns: 24px 32px 60px minmax(140px, 1.2fr) 55px 65px 55px 55px 50px 65px 55px 85px 65px minmax(80px, 1fr) minmax(120px, 1fr) 50px;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
padding: 8px 10px;
|
||||
@ -1543,7 +1544,7 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
|
||||
.voices-workbench.table-view .vl-compact {
|
||||
display: grid;
|
||||
grid-template-columns: 24px 32px 60px minmax(140px, 1.2fr) 55px 65px 50px 55px 55px 55px 55px 50px 65px 55px 85px 65px minmax(80px, 1fr) minmax(120px, 1fr) 50px;
|
||||
grid-template-columns: 24px 32px 60px minmax(140px, 1.2fr) 55px 65px 55px 55px 50px 65px 55px 85px 65px minmax(80px, 1fr) minmax(120px, 1fr) 50px;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
@ -1556,24 +1557,20 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
.voices-workbench.table-view .vl-name-input { order: 3; display: block; font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text); }
|
||||
.voices-workbench.table-view .vl-tbl-lang { display: block; order: 4; font-size: 12px; }
|
||||
.voices-workbench.table-view .vl-tbl-gender { display: block; order: 5; font-size: 12px; }
|
||||
.voices-workbench.table-view .vl-tbl-len { display: block; order: 6; font-size: 12px; font-family: monospace; }
|
||||
.voices-workbench.table-view .vl-tbl-dur { display: block; order: 7; font-size: 12px; font-family: monospace; }
|
||||
.voices-workbench.table-view .vl-tbl-factor { display: block; order: 8; font-size: 12px; font-family: monospace; font-weight: 600; }
|
||||
.voices-workbench.table-view .vl-tbl-time { display: block; order: 9; font-size: 12px; font-family: monospace; }
|
||||
.voices-workbench.table-view .vl-tbl-wpm { display: block; order: 10; font-size: 12px; font-family: monospace; }
|
||||
.voices-workbench.table-view .vl-tbl-seed { display: block; order: 11; font-size: 12px; font-family: monospace; }
|
||||
.voices-workbench.table-view .vl-tbl-dbfs { display: block; order: 12; font-size: 12px; font-family: monospace; }
|
||||
.voices-workbench.table-view .vl-tbl-type { display: block; order: 13; font-size: 12px; }
|
||||
.voices-workbench.table-view .vl-tbl-source { display: block; order: 14; font-size: 12px; overflow: hidden; text-overflow: ellipsis; }
|
||||
.voices-workbench.table-view .vl-tbl-rating { display: flex; order: 15; font-size: 14px; color: var(--subtext); }
|
||||
.voices-workbench.table-view .vl-tbl-factor { display: block; order: 6; font-size: 12px; font-family: monospace; font-weight: 600; }
|
||||
.voices-workbench.table-view .vl-tbl-wpm { display: block; order: 7; font-size: 12px; font-family: monospace; }
|
||||
.voices-workbench.table-view .vl-tbl-seed { display: block; order: 8; font-size: 12px; font-family: monospace; }
|
||||
.voices-workbench.table-view .vl-tbl-dbfs { display: block; order: 9; font-size: 12px; font-family: monospace; }
|
||||
.voices-workbench.table-view .vl-tbl-type { display: block; order: 10; font-size: 12px; }
|
||||
.voices-workbench.table-view .vl-tbl-source { display: block; order: 11; font-size: 12px; overflow: hidden; text-overflow: ellipsis; }
|
||||
.voices-workbench.table-view .vl-tbl-rating { display: flex; order: 12; font-size: 14px; color: var(--subtext); }
|
||||
.voices-workbench.table-view .vl-tbl-rating .star.on { color: #f59e0b; }
|
||||
.voices-workbench.table-view .vl-tbl-tag { display: block; order: 16; font-size: 12px; color: var(--accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
|
||||
.voices-workbench.table-view .vl-tbl-note { display: block; order: 17; font-size: 12px; color: var(--subtext); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-style: italic; }
|
||||
.voices-workbench.table-view .vl-tbl-active { display: block; order: 18; font-size: 16px; text-align: center; }
|
||||
.voices-workbench.table-view .vl-tbl-tag { display: block; order: 13; font-size: 12px; color: var(--accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
|
||||
.voices-workbench.table-view .vl-tbl-note { display: block; order: 14; font-size: 12px; color: var(--subtext); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-style: italic; }
|
||||
.voices-workbench.table-view .vl-tbl-active { display: block; order: 15; font-size: 16px; text-align: center; }
|
||||
.voices-workbench.table-view .vl-tbl-factor.bench-ok { color: var(--success, #16a34a); }
|
||||
.voices-workbench.table-view .vl-tbl-factor.bench-warn { color: #d97706; }
|
||||
.voices-workbench.table-view .vl-tbl-factor.bench-bad { color: var(--error, #dc2626); }
|
||||
.voices-workbench.table-view .vl-tbl-time.bench-bad { color: var(--error, #dc2626); }
|
||||
|
||||
.voices-workbench.table-edit-mode .vl-compact {
|
||||
background: rgba(37,99,235,0.03);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user