feat: show analysis progress with per-file counter and cached tally

Day highlights: replaces the static "Analysing N files…" spinner with a
live progress bar, current filename ("3 / 8 — recording.wav"), and a
running tally of cached vs freshly-analysed files. Completes with a
summary line ("Done — 8 files (6 cached, 2 analysed)").

Single-file analyse: adds a small "cached" badge in the waveform box
when the result was served from cache.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 22:40:19 +02:00
parent 75434ca96d
commit 7821f8823d
+35 -3
View File
@@ -704,6 +704,11 @@ button.chip:hover{background:#6c1f08;border-color:#9a3412}
button.chip:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.quiet{color:var(--muted);font-size:12px;margin-top:6px}
.spin{color:var(--muted);font-style:italic;font-size:12px;padding:6px 0}
.prog-bar{height:3px;background:var(--brd);border-radius:2px;margin:6px 0 4px;overflow:hidden}
.prog-fill{height:100%;background:var(--accent);border-radius:2px;transition:width 0.15s ease}
.prog-file{font-size:12px;color:var(--muted);font-style:italic;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
.prog-tally{font-size:11px;color:var(--muted);margin-top:3px}
.cached-badge{font-size:10px;color:var(--muted);background:var(--surf);border:1px solid var(--brd);border-radius:3px;padding:1px 5px;margin-left:6px;vertical-align:middle;font-style:normal}
.empty{text-align:center;padding:60px;color:var(--muted)}
/* player row */
.player-row td{padding:0 10px 10px;background:var(--bg);border-bottom:1px solid var(--brd)}
@@ -955,6 +960,12 @@ async function analyse(idx, filename, cell, btn) {
}
const box = document.createElement('div'); box.className='wbox';
box.appendChild(drawWave(d.rms_display||[], d.sections||[], d.duration||0, filename));
if (d.cached) {
const badge = document.createElement('span');
badge.className = 'cached-badge'; badge.textContent = 'cached';
badge.title = 'Result loaded from cache — change threshold/gap and re-analyse to recompute';
box.firstChild.after(badge);
}
const chips = document.createElement('div');
chips.className='chips';
@@ -1323,22 +1334,43 @@ async function dayHighlights(dayId, analyzableFiles) {
hlRow.hidden = false;
const n = analyzableFiles.length;
contentEl.innerHTML = `<div class="spin" aria-live="polite" aria-busy="true">Analysing ${n} file${n!==1?'s':''}…</div>`;
if (btn) btn.disabled = true;
// Build progress UI
const progWrap = document.createElement('div');
progWrap.setAttribute('aria-live', 'polite'); progWrap.setAttribute('aria-busy', 'true');
const progBar = document.createElement('div'); progBar.className = 'prog-bar';
const progFill = document.createElement('div'); progFill.className = 'prog-fill'; progFill.style.width = '0%';
progBar.appendChild(progFill);
const progFile = document.createElement('div'); progFile.className = 'prog-file';
const progTally = document.createElement('div'); progTally.className = 'prog-tally';
progWrap.appendChild(progBar); progWrap.appendChild(progFile); progWrap.appendChild(progTally);
contentEl.innerHTML = ''; contentEl.appendChild(progWrap);
const threshold = document.getElementById('threshold-input').value || '0.05';
const minGap = document.getElementById('min-gap-input').value || '2';
const results = [];
for (const f of analyzableFiles) {
let nCached = 0, nLive = 0;
for (let i = 0; i < analyzableFiles.length; i++) {
const f = analyzableFiles[i];
progFile.textContent = `${i + 1} / ${n} — ${f.name}`;
progFill.style.width = `${(i / n) * 100}%`;
const tallyParts = [];
if (nCached) tallyParts.push(`${nCached} cached`);
if (nLive) tallyParts.push(`${nLive} analysed`);
progTally.textContent = tallyParts.join(' · ');
try {
const r = await fetch('/api/analyze?file=' + encodeURIComponent(f.name)
+ '&threshold=' + encodeURIComponent(threshold)
+ '&min_gap=' + encodeURIComponent(minGap));
const d = await r.json();
if (!d.error) results.push({ f, data: d });
if (!d.error) { results.push({ f, data: d }); d.cached ? nCached++ : nLive++; }
} catch(e) {}
}
progFill.style.width = '100%';
progFile.textContent = `Done — ${n} file${n!==1?'s':''} (${nCached} cached, ${nLive} analysed)`;
progTally.textContent = '';
if (!results.length) {
contentEl.innerHTML = '<div class="quiet">No analysable results.</div>';