feat: casting badge, navigate-away/back, live character count (v1.12.3)
- Pulsing blue dot on the Read Aloud nav item while casting is active - Navigating away and returning restores the cast panel automatically - Reassigning a segment's speaker decrements the old count so the Characters Found panel stays in sync; speakers at 0 disappear Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4f795e820c
commit
7a75d1ecf4
11
CHANGELOG.md
11
CHANGELOG.md
@ -9,6 +9,17 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
||||
|
||||
---
|
||||
|
||||
## [1.12.3] — 2026-06-27
|
||||
|
||||
### Added
|
||||
- **Casting activity indicator**: pulsing blue dot on the Read Aloud nav item while audiobook casting is in progress, so users can see it's working from any section.
|
||||
- **Restore cast view on return**: navigating away during casting and then back to Read Aloud automatically restores the casting panel instead of showing the blank PDF view.
|
||||
|
||||
### Fixed
|
||||
- **Character count out of sync on reassignment**: reassigning a segment's speaker now decrements the old speaker's count in the Characters Found panel, so totals stay accurate when corrections are made. Speakers that drop to 0 lines are removed from the panel automatically.
|
||||
|
||||
---
|
||||
|
||||
## [1.12.2] — 2026-06-27
|
||||
|
||||
### Added
|
||||
|
||||
@ -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.2">
|
||||
<link rel="stylesheet" href="/static/style.css?v=1.12.3">
|
||||
|
||||
|
||||
<!-- ── 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.2"></script>
|
||||
<script src="/static/loader.js?v=1.12.3"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -290,6 +290,7 @@ STRIKTE FORMAT- UND TEXTREGELN:
|
||||
const closePanel = () => {
|
||||
panel.hidden = true;
|
||||
panel.innerHTML = '';
|
||||
if (typeof window.setNavCastingBadge === 'function') window.setNavCastingBadge(false);
|
||||
if (typeof window.navReaderView === 'function') window.navReaderView('main');
|
||||
else if (typeof window.showReaderView === 'function') window.showReaderView('main');
|
||||
else {
|
||||
@ -497,7 +498,7 @@ STRIKTE FORMAT- UND TEXTREGELN:
|
||||
return html;
|
||||
};
|
||||
const renderRoster = () => {
|
||||
const items = [...roster.entries()].sort((a, b) => b[1].count - a[1].count);
|
||||
const items = [...roster.entries()].filter(([, info]) => info.count > 0).sort((a, b) => b[1].count - a[1].count);
|
||||
chars.innerHTML = items.length
|
||||
? items.map(([n, info]) => `<span class="ab-chip" style="--c:${info.color}"><span class="ab-chip-dot"></span>${escHtml(n)}<b>${info.count}</b></span>`).join('')
|
||||
: '<span class="ab-cv-empty">reading…</span>';
|
||||
@ -618,6 +619,12 @@ STRIKTE FORMAT- UND TEXTREGELN:
|
||||
function assignName(name) {
|
||||
if (!assignModeSeg) return;
|
||||
const isNarrator = name.toLowerCase() === 'narrator';
|
||||
// Decrement old speaker's count before reassigning
|
||||
const _oldSpk = assignModeSeg.speaker;
|
||||
if (_oldSpk && roster.has(_oldSpk)) {
|
||||
const _old = roster.get(_oldSpk);
|
||||
if (_old.count > 0) _old.count--;
|
||||
}
|
||||
assignModeSeg.speaker = isNarrator ? 'Narrator' : name;
|
||||
assignModeSeg.type = isNarrator ? 'narration' : 'dialogue';
|
||||
if (isNarrator) assignModeSeg.emotion = '';
|
||||
@ -965,6 +972,7 @@ ABSOLUTE REGELN:
|
||||
foot.querySelector('#ab-cv-recast').addEventListener('click', () => applyPromptAndRun(audiobookCast));
|
||||
foot.querySelector('#ab-cv-recast-unk').addEventListener('click', () => applyPromptAndRun(audiobookRecastUnknown));
|
||||
panel.classList.add('ab-castpanel-done');
|
||||
if (typeof window.setNavCastingBadge === 'function') window.setNavCastingBadge(false);
|
||||
},
|
||||
done() { closePanel(); },
|
||||
};
|
||||
@ -988,12 +996,13 @@ async function audiobookRecastUnknown(overrideUrl, overrideModel) {
|
||||
}
|
||||
|
||||
_audiobook.running = true; _audiobook.cancel = false;
|
||||
if (typeof window.setNavCastingBadge === 'function') window.setNavCastingBadge(true);
|
||||
const ac = new AbortController(); _audiobook.abort = () => ac.abort();
|
||||
|
||||
|
||||
if (overrideUrl && typeof overrideUrl !== 'string') overrideUrl = null;
|
||||
const llm_url = overrideUrl || audiobookLlmUrl(), language = audiobookLang();
|
||||
let model = overrideModel || audiobookLlmModel();
|
||||
|
||||
|
||||
const view = audiobookCastView(unknownIdxs.length, llm_url, model);
|
||||
|
||||
view.processing('Waking up LLM model (this may take a few minutes if cold-booting)…');
|
||||
@ -1118,9 +1127,10 @@ async function audiobookCast(overrideUrl, overrideModel) {
|
||||
: [text];
|
||||
|
||||
_audiobook.running = true; _audiobook.cancel = false;
|
||||
if (typeof window.setNavCastingBadge === 'function') window.setNavCastingBadge(true);
|
||||
const ac = new AbortController();
|
||||
_audiobook.abort = () => ac.abort();
|
||||
|
||||
|
||||
if (overrideUrl && typeof overrideUrl !== 'string') overrideUrl = null;
|
||||
const llm_url = overrideUrl || audiobookLlmUrl(), language = audiobookLang();
|
||||
let model = overrideModel || audiobookLlmModel();
|
||||
|
||||
@ -124,6 +124,15 @@
|
||||
// Leaving the Read Aloud reader: stop playback so audio doesn't keep running
|
||||
if (sectionId !== 's-reader' && typeof window.readerStop === 'function') window.readerStop();
|
||||
if (sectionId === 's-reader' && typeof window.readerOnShow === 'function') window.readerOnShow();
|
||||
// If a cast session panel is active, restore it instead of showing the main PDF view
|
||||
if (sectionId === 's-reader') {
|
||||
var _castPnl = document.getElementById('reader-audiobook-panel');
|
||||
if (_castPnl && _castPnl.querySelector('#ab-cv-feed')) {
|
||||
setTimeout(function () {
|
||||
if (typeof window.showReaderView === 'function') window.showReaderView('cast');
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
if (sectionId === 's-library' && typeof window.libraryRender === 'function') window.libraryRender(window._libraryView || 'books');
|
||||
if (typeof window.initCollapsibleCards === 'function') window.initCollapsibleCards();
|
||||
setStoredSection(sectionId);
|
||||
@ -163,6 +172,20 @@
|
||||
if (tabName) runSideEffects(tabName);
|
||||
};
|
||||
|
||||
// Pulsing dot on the Read Aloud nav item while casting is in progress
|
||||
window.setNavCastingBadge = function (active) {
|
||||
var el = document.querySelector('[data-nav-section="s-reader"]');
|
||||
if (!el) return;
|
||||
var existing = el.querySelector('.nav-casting-dot');
|
||||
if (active && !existing) {
|
||||
var dot = document.createElement('span');
|
||||
dot.className = 'nav-casting-dot';
|
||||
el.appendChild(dot);
|
||||
} else if (!active && existing) {
|
||||
existing.remove();
|
||||
}
|
||||
};
|
||||
|
||||
window.switchTab = function (name) {
|
||||
var sectionId = TAB_SECTION_MAP[name];
|
||||
if (sectionId) showSection(sectionId);
|
||||
|
||||
@ -176,6 +176,7 @@ body {
|
||||
border-radius: 10px; padding: 1px 6px; min-width: 18px; text-align: center;
|
||||
}
|
||||
.nav-chevron { font-size: 14px; color: var(--subtext); transition: transform .2s; margin-left: 2px; }
|
||||
.nav-casting-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); flex-shrink: 0; margin-left: auto; animation: pulse 1s infinite; }
|
||||
.nav-chevron.open { transform: rotate(180deg); }
|
||||
.nav-tree { display: none; flex-direction: column; padding: 2px 0 6px; }
|
||||
.nav-tree.open { display: flex; }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user