Fix quality-run roster display, blank API errors, resolver-first (v1.12.94)
The recast/quality view's sidebar rebuilt its roster from only the lines being checked, appearing to wipe every named character (the cast itself was safe: named lines are never recast targets and increases in Unknowns already roll back). The sidebar now seeds from the full cast and refreshes during the run. Error notes fall back to the HTTP status (statusText is empty on HTTP/2). The deterministic grammar resolver runs before any LLM call in quality runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
42a45c43b6
commit
d1969b7609
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.94] — 2026-07-04
|
||||
|
||||
### Fixed
|
||||
- **2nd Quality Run / Recast unknown appeared to wipe all named characters** — the sidebar roster was rebuilt only from the lines being checked, so it collapsed to "Unknown <n>" as if the whole cast was lost. The cast itself was never touched (named lines are never targets, and a run that would increase Unknowns already rolls back completely) — but the display now seeds from the full cast and stays correct as the run progresses.
|
||||
- **"API Error while checking Unknown line:" with no reason** — `statusText` is empty on HTTP/2, so error notes ended blank; they now fall back to the HTTP status code.
|
||||
|
||||
### Changed
|
||||
- **Quality runs apply the grammar rules first** — the deterministic resolver (colon rule, post-quote inquit, "who had spoken") now runs before any LLM call in Recast unknown / 2nd Quality Run, resolving the mechanical cases instantly and shrinking the LLM's queue.
|
||||
|
||||
---
|
||||
|
||||
## [1.12.93] — 2026-07-04
|
||||
|
||||
### Added
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<meta name="theme-color" content="#2563EB">
|
||||
<meta name="app-version" content="1.12.93">
|
||||
<meta name="app-version" content="1.12.94">
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
<link rel="icon" href="/static/icon.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="/static/icon.svg">
|
||||
@ -27,7 +27,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.93">
|
||||
<link rel="stylesheet" href="/static/style.css?v=1.12.94">
|
||||
|
||||
|
||||
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
|
||||
@ -365,7 +365,7 @@ window.toggleNavTree = function(treeId, chevronId) {
|
||||
</script>
|
||||
|
||||
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
|
||||
<script src="/static/loader.js?v=1.12.93"></script>
|
||||
<script src="/static/loader.js?v=1.12.94"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -2663,6 +2663,10 @@ STRIKTE FORMAT- UND TEXTREGELN:
|
||||
});
|
||||
|
||||
return {
|
||||
// Refresh the sidebar roster from the FULL cast — recast/quality runs feed
|
||||
// the view only the lines they check, which made the sidebar collapse to
|
||||
// "Unknown <n>" as if all named characters had been lost.
|
||||
recountRoster(allSegs) { _abRecountRoster(allSegs || []); },
|
||||
rebuild(segs) {
|
||||
this.clearProcessing();
|
||||
feed.querySelector('.ab-skel-feed')?.remove();
|
||||
@ -3042,6 +3046,11 @@ async function audiobookRecastUnknown(overrideUrl, overrideModel, options = {})
|
||||
const countUnknownDialogue = arr => (arr || []).filter(s => s?.type === 'dialogue' && (!s.speaker || /^Unknown|Unbekannt/i.test(s.speaker))).length;
|
||||
const beforeUnknownCount = countUnknownDialogue(segs);
|
||||
|
||||
// Mechanical cases first (colon rule, post-quote inquit) — resolved in code
|
||||
// for free, so they never even reach the LLM queue below.
|
||||
const preResolved = audiobookResolveUnknowns(segs, [], _audiobook.roster || []);
|
||||
if (preResolved.length) toast(`${preResolved.length} Unknown line${preResolved.length !== 1 ? 's' : ''} resolved by grammar rules`, 'success');
|
||||
|
||||
const unknownIdxs = [];
|
||||
for (let i = 0; i < segs.length; i++) {
|
||||
if (segs[i].type === 'dialogue' && (!segs[i].speaker || /^Unknown|Unbekannt/i.test(segs[i].speaker))) {
|
||||
@ -3065,6 +3074,7 @@ async function audiobookRecastUnknown(overrideUrl, overrideModel, options = {})
|
||||
|
||||
const groups = audiobookRecastGroups(unknownIdxs, segs);
|
||||
const view = audiobookCastView(unknownIdxs.length, llm_url, model);
|
||||
view.recountRoster(segs); // sidebar shows the whole cast's roster, not just the lines being checked
|
||||
|
||||
view.processing('Waking up LLM model (this may take a few minutes if cold-booting)…');
|
||||
try {
|
||||
@ -3157,11 +3167,12 @@ async function audiobookRecastUnknown(overrideUrl, overrideModel, options = {})
|
||||
timeout_seconds: audiobookTimeoutSeconds(AUDIOBOOK_RECAST_TIMEOUT_MS)
|
||||
}, promptOverride))
|
||||
}, AUDIOBOOK_RECAST_TIMEOUT_MS + 5000);
|
||||
if (!r.ok) { const e = await r.json().catch(() => ({})); throw new Error(e.detail || e.error || r.statusText); }
|
||||
if (!r.ok) { const e = await r.json().catch(() => ({})); throw new Error(e.detail || e.error || r.statusText || ('HTTP ' + r.status)); }
|
||||
data = await r.json();
|
||||
} catch (err) {
|
||||
if (err.name === 'AbortError') { _audiobook.cancel = true; break; }
|
||||
view.note(`API Error while checking ${group.length > 1 ? 'a group of Unknown lines' : 'Unknown line'}: ${err.message}`);
|
||||
// statusText is empty on HTTP/2, which used to leave this note blank after the colon
|
||||
view.note(`API Error while checking ${group.length > 1 ? 'a group of Unknown lines' : 'Unknown line'}: ${err.message || err}`);
|
||||
for (const idx of group) {
|
||||
view.addSegments([segs[idx]]);
|
||||
done++;
|
||||
@ -3207,6 +3218,7 @@ async function audiobookRecastUnknown(overrideUrl, overrideModel, options = {})
|
||||
if (groupsSinceSave >= 10 && _audiobook.lastText) {
|
||||
groupsSinceSave = 0;
|
||||
_abSaveDraft(_audiobook.segments || [], _audiobook.roster || [], _audiobook.lastText, _audiobook.completedChunks || 0, _audiobook.completedTotal || 0);
|
||||
view.recountRoster(segs); // keep the sidebar reflecting the full cast as lines resolve
|
||||
}
|
||||
}
|
||||
view.update(_audiobook.cancel ? done : unknownIdxs.length);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user