feat: add WPM column + info tooltips to voice table (v1.12.10)
WPM (words per minute) is derived from the benchmark audio duration and sentence word count, revealing how fast a voice speaks — independent of GPU speed. 130–180 wpm is comfortable for audiobooks. Column is sortable. Info (ⓘ) icons on Speed and WPM headers explain both metrics on hover. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
73d991c2f8
commit
c5181b499a
@ -9,6 +9,14 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
||||
|
||||
---
|
||||
|
||||
## [1.12.10] — 2026-06-27
|
||||
|
||||
### Added
|
||||
- **WPM column** in the voice table — shows how fast a voice speaks in words per minute, calculated from the benchmark audio duration and sentence word count. Sortable. Hover for a tooltip explaining the range (130–180 wpm is natural for audiobooks).
|
||||
- **Info (ⓘ) icons** on the Speed and WPM column headers with tooltips explaining what each metric measures.
|
||||
|
||||
---
|
||||
|
||||
## [1.12.9] — 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.9">
|
||||
<link rel="stylesheet" href="/static/style.css?v=1.12.10">
|
||||
|
||||
|
||||
<!-- ── 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.9"></script>
|
||||
<script src="/static/loader.js?v=1.12.10"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -98,6 +98,7 @@ function getSortValue(v, field) {
|
||||
case 'duration': return v.duration || 0;
|
||||
case 'dbfs': return voiceDbfs(v) ?? -999;
|
||||
case 'benchmark': { const b = voiceBenchmark(v); return b && b.speed != null ? -Number(b.speed) : 999; }
|
||||
case 'wpm': return voiceWpm(v) ?? -1;
|
||||
case 'transcript': return (v.transcript || '').toLowerCase();
|
||||
case 'note': return (v.note || '').toLowerCase();
|
||||
case 'source': return (_displaySource(v) || '').toLowerCase();
|
||||
@ -376,6 +377,18 @@ function benchmarkClass(v) {
|
||||
return elapsed != null && elapsed <= 4 ? 'bench-ok' : 'bench-warn';
|
||||
}
|
||||
|
||||
function voiceWpm(v) {
|
||||
const b = voiceBenchmark(v);
|
||||
if (!b || !b.ok || !b.audio_sec || !b.text) return null;
|
||||
const words = b.text.trim().split(/\s+/).length;
|
||||
return Math.round(words / (b.audio_sec / 60));
|
||||
}
|
||||
|
||||
function fmtWpm(v) {
|
||||
const wpm = voiceWpm(v);
|
||||
return wpm != null ? wpm + ' wpm' : '-';
|
||||
}
|
||||
|
||||
function voiceFileUrl(v) {
|
||||
const version = v._audioVersion || v.updated_at || v.benchmarked_at || '';
|
||||
const bust = version || Date.now();
|
||||
@ -1951,6 +1964,7 @@ function makeVoiceRow(v) {
|
||||
<div class="vl-tbl-bench">${benchText !== '-' ? `<span class="vl-bench-chip ${benchCls}" title="${escHtml(benchTitle)}"><span class="mdi mdi-timer-outline"></span> ${escHtml(benchText)}</span>` : '-'}</div>
|
||||
<div class="vl-tbl-dbfs" title="${dbTitle}">${dbfs} dB</div>
|
||||
<div class="vl-tbl-len">${fmtDuration(v.duration)}</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-rating">
|
||||
${window._voiceTableEditMode
|
||||
? `<input type="number" min="0" max="5" class="vl-inline-edit" data-field="rating" value="${v.rating||0}" style="width:40px">`
|
||||
|
||||
@ -109,9 +109,10 @@
|
||||
<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>Type</div>
|
||||
<div class="vl-th-sortable" data-sort="benchmark">Speed <span class="mdi mdi-sort"></span></div>
|
||||
<div class="vl-th-sortable" data-sort="benchmark">Speed <span class="mdi mdi-sort"></span><span class="vl-th-info mdi mdi-information-outline" title="Synthesis speed: seconds of audio produced per second of compute (higher = faster GPU). Shown as: render_time · multiplier×"></span></div>
|
||||
<div class="vl-th-sortable" data-sort="dbfs">dBFS <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="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="rating">Rating <span class="mdi mdi-sort"></span></div>
|
||||
<div class="vl-th-sortable" data-sort="source">Source <span class="mdi mdi-sort"></span></div>
|
||||
<div class="vl-th-sortable" data-sort="seed">Seed <span class="mdi mdi-sort"></span></div>
|
||||
|
||||
@ -1516,7 +1516,7 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
.vl-dbfs { display: none; }
|
||||
|
||||
/* Table View mode */
|
||||
.vl-tbl-lang, .vl-tbl-gender, .vl-tbl-type, .vl-tbl-bench, .vl-tbl-dbfs, .vl-tbl-len, .vl-tbl-rating, .vl-tbl-source, .vl-tbl-seed, .vl-tbl-note, .vl-tbl-tag, .vl-tbl-active { display: none; }
|
||||
.vl-tbl-lang, .vl-tbl-gender, .vl-tbl-type, .vl-tbl-bench, .vl-tbl-dbfs, .vl-tbl-len, .vl-tbl-wpm, .vl-tbl-rating, .vl-tbl-source, .vl-tbl-seed, .vl-tbl-note, .vl-tbl-tag, .vl-tbl-active { display: none; }
|
||||
.vl-table-header { display: none; }
|
||||
|
||||
.voices-workbench.table-view .voices-list-pane { width: 100% !important; max-width: 100%; }
|
||||
@ -1524,7 +1524,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) 60px 70px 60px 80px 70px 60px 90px 70px 60px minmax(120px, 1fr) minmax(80px, 1fr) 50px;
|
||||
grid-template-columns: 24px 32px 60px minmax(140px, 1.2fr) 60px 70px 60px 80px 70px 60px 55px 90px 70px 60px minmax(120px, 1fr) minmax(80px, 1fr) 50px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
padding: 8px 10px;
|
||||
@ -1538,10 +1538,12 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
.vl-th-sortable:hover { color: var(--text); }
|
||||
.vl-th-sortable .mdi { font-size: 12px; opacity: .6; }
|
||||
.vl-th-sortable:hover .mdi { opacity: 1; color: var(--accent); }
|
||||
.vl-th-info { font-size: 11px !important; opacity: .5; cursor: help; margin-left: 1px; }
|
||||
.vl-th-info:hover { opacity: 1; color: var(--accent); }
|
||||
|
||||
.voices-workbench.table-view .vl-compact {
|
||||
display: grid;
|
||||
grid-template-columns: 24px 32px 60px minmax(140px, 1.2fr) 60px 70px 60px 80px 70px 60px 90px 70px 60px minmax(120px, 1fr) minmax(80px, 1fr) 50px;
|
||||
grid-template-columns: 24px 32px 60px minmax(140px, 1.2fr) 60px 70px 60px 80px 70px 60px 55px 90px 70px 60px minmax(120px, 1fr) minmax(80px, 1fr) 50px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
@ -1558,13 +1560,14 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
.voices-workbench.table-view .vl-tbl-bench { display: block; order: 7; font-size: 12px; }
|
||||
.voices-workbench.table-view .vl-tbl-dbfs { display: block; order: 8; font-size: 12px; font-family: monospace; }
|
||||
.voices-workbench.table-view .vl-tbl-len { display: block; order: 9; font-size: 12px; font-family: monospace; }
|
||||
.voices-workbench.table-view .vl-tbl-rating { display: flex; order: 10; font-size: 14px; color: var(--subtext); }
|
||||
.voices-workbench.table-view .vl-tbl-wpm { display: block; order: 10; font-size: 12px; font-family: monospace; }
|
||||
.voices-workbench.table-view .vl-tbl-rating { display: flex; order: 11; font-size: 14px; color: var(--subtext); }
|
||||
.voices-workbench.table-view .vl-tbl-rating .star.on { color: #f59e0b; }
|
||||
.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-seed { display: block; order: 12; font-size: 12px; font-family: monospace; }
|
||||
.voices-workbench.table-view .vl-tbl-note { display: block; order: 13; font-size: 12px; color: var(--subtext); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-style: italic; }
|
||||
.voices-workbench.table-view .vl-tbl-tag { display: block; order: 14; font-size: 12px; color: var(--accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
|
||||
.voices-workbench.table-view .vl-tbl-active { display: block; order: 15; font-size: 16px; text-align: center; }
|
||||
.voices-workbench.table-view .vl-tbl-source { display: block; order: 12; font-size: 12px; overflow: hidden; text-overflow: ellipsis; }
|
||||
.voices-workbench.table-view .vl-tbl-seed { display: block; order: 13; font-size: 12px; font-family: monospace; }
|
||||
.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-tag { display: block; order: 15; font-size: 12px; color: var(--accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
|
||||
.voices-workbench.table-view .vl-tbl-active { display: block; order: 16; font-size: 16px; text-align: center; }
|
||||
|
||||
.voices-workbench.table-edit-mode .vl-compact {
|
||||
background: rgba(37,99,235,0.03);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user