From 7821f8823d05f08ffe6e8fdb634e7b5d2908aadc Mon Sep 17 00:00:00 2001 From: jonathan Date: Tue, 2 Jun 2026 22:40:19 +0200 Subject: [PATCH] feat: show analysis progress with per-file counter and cached tally MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- web.py | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/web.py b/web.py index 2963a0a..6524d9b 100644 --- a/web.py +++ b/web.py @@ -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 = `
Analysing ${n} file${n!==1?'s':''}…
`; 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 = '
No analysable results.
';