Commit Graph

190 Commits

Author SHA1 Message Date
5fecbf06d4 Fix Fish-Speech emotion tags, wire book context into portraits, add emotion controls app-wide (v1.20.5)
Fish-Speech emotion tags were silently ignored on non-English books: per-line
emotions are LLM-generated in the book's own language, but Fish-Speech only
recognizes English [tag] markers, and a double-tagging bug was stacking a
broken server-derived tag on top of the client's own. Added a DE->EN
translation table and removed the double-tagging. Also wires the existing
book-profile context and race_species field into character portrait prompts
(previously only used for voice design), adds a recast-until-threshold loop
for casting, and adds backend-aware emotion quick-picks to Read Aloud, Try a
Voice, and Conversation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 18:02:41 +02:00
ac115b25b9 Store the full voice design prompt, not just the clipped summary
For a designed voice the instruct prompt is the voice's identity — the TTS
engine reproduces the voice from that text alone. The only copy saved was the
`note` display summary, clipped to 240 characters, which left 43 of 73 voices
cut off mid-sentence. Save the complete prompt in its own field so the engine
can register a voice from the whole description.

Existing voices keep working from the clipped copy (it still carries gender,
accent and timbre) and pick up the full text when next redesigned.

Pairs with the engine-side fix in tts-dgx-spark-faster-qwen3-tts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 15:57:51 +02:00
a62dd0bac1 Fix voice stability, audio effects, and character/voice pipeline bugs
Voice consistency:
- Read back each voice's pinned seed (Seed Finder / Batch Seeds) on every
  generation. The seed was saved to voice metadata but only ever read by the
  Seed Finder's own benchmark path, so all per-voice seed pinning was inert.
- Stop coercing the "voice_design_playback" stability profile back to
  "voice_clone". The pseudo-backend key isn't a real routing target, so the
  backend-name normalizer silently rewrote it — reintroducing the hardcoded
  seed:0 that profile exists to avoid, overriding every per-voice pin.
- Apply the accent clause on every line, not just at voice-creation time,
  and reorder the instruct so emotion leads and accent trails (Qwen3-TTS
  doesn't reliably follow multiple conflicting instructions).
- Pass an explicit language to Voice Design instead of leaving it on "Auto".

Audio effects:
- Add a limiter after compressor makeup gain. Makeup gain pushed peaks to
  ~1.9, and the final hard clip turned that into broadband distortion that
  swamped the rest of the chain.
- Cascade highpass/lowpass 3 stages each (~18 dB/octave). Single-pole
  filters were too gentle to band-limit speech audibly.
- Add a Bandpass control and wire it into the Telephone/Radio presets —
  compression alone never sounded like a phone; band-limiting is the
  defining trait.

Persona / Try It Out:
- Disable "Apply character persona" with an explanatory tooltip when the
  voice has no persona saved, and error clearly server-side instead of
  silently no-op'ing. Persona is typed manually per voice, never auto-filled.
- Stop dropping applyPersona in the chunked generation path (>200 chars).
- Populate the Voice Design dropdown from the user's own library rather than
  filtering the engine's discovery list, which never contains custom voices.

Navigation and library:
- Use pushState instead of replaceState so browser Back/Forward step through
  in-app navigation instead of leaving the app entirely.
- Show real dialogue line counts in the character sidebar instead of the
  capped reference-quote count (which showed a misleading uniform "12").

Also fixes a crash in /api/transcribe-bytes that referenced an undefined
source_id in its cleanup path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 15:33:24 +02:00
ea50267c30 Add unified Studio casting workflow and fix voice/casting pipeline bugs
Introduces the new Studio section (Source -> Characters -> Voices ->
Perform & Export) that reuses the existing Read Aloud/Library/Script
Rehearsal code via DOM reparenting instead of duplicating it, and rolls up
a long tail of bugs found while producing a real audiobook through it:
umlaut-eating name sanitizers, a voice picker that mispositioned itself and
capped results at 60, PDF pagination silently breaking on trimmed \f
markers, a race letting stale audio keep playing after a new line was
clicked, an alias-overlap bug that could silently redirect a voice/image
save onto the wrong character, voice design failing outright during brief
TTS backend restarts instead of retrying, sparse cast entries defaulting to
English/wrong gender, and a reassigned voice never reaching an already-open
Stage session or invalidating its cached audio. Also adds a persistent
per-line audio cache, audiobook export browsing/download, and an inline
voice-design prompt editor. Full details in CHANGELOG.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-26 02:03:56 +02:00
f6e5449eb6 Show source evidence in character sheets 2026-07-09 20:36:17 +02:00
ab58ef2e43 Fix character sheets layout nesting 2026-07-09 20:14:03 +02:00
588ba3fcb8 Refine character sheets and live output UI 2026-07-09 19:47:16 +02:00
8f3fcbae04 Release v1.14.19 2026-07-09 14:11:40 +02:00
17716ac740 Stop retried/recast audiobook casts from creating duplicate scripts (v1.14.18)
A fresh (non-resume) cast unconditionally nulled _audiobook.rehId even
when retrying or recasting a book that already had a linked Rehearsal
Library record from a prior attempt — silently orphaning it and
creating a new one on the next save. Now only nulled for a genuinely
first-ever cast; retries/recasts overwrite the existing record.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 12:23:55 +02:00
606ae125a3 Add editable Prompt panel for Character sheets (v1.14.17)
Same pattern as the Casting audiobook Prompt button: a collapsible
textarea with the full extraction prompt (fields + sources/citation
schema), named-preset save/load/delete, and live persistence so edits
apply from the next passage onward. Server-side, _charsheets_prepare()
now accepts a character_sheets_prompt override (shared by the blocking
and streaming endpoints), following the same pattern already used for
the audiobook attribution prompt override.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 12:21:26 +02:00
b5ffa655d8 Redesign Character sheets progress modal into a big split view (v1.14.16)
Was a small 420px dialog with a collapsible one-line JSON preview and a
cramped 2-column chip grid. Now a big panel matching the audiobook
casting live view's shape: passage text (left) and live JSON output
(right) side by side, with a character sidebar on the right edge whose
just-updated entries are highlighted — so the user can watch each sheet
actually being built from its source passage instead of a tiny opaque
progress bar.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:53:57 +02:00
b96968ac5d Add mid-quote-dash and voice-announcement rules to casting attribution prompt (v1.14.15)
Two gaps observed in real casting output:
1. A mid-quote " - " (em-dash pause) was being read as the end of the
   quotation, handing the rest of the line to a wrong/new speaker.
2. Narration naming whose voice is about to speak (e.g. "Marcians
   Stimme wirkte nicht mehr so fest") wasn't being used to resolve the
   following unattributed line away from 'Unknown'.

Added as rules 11/12 to the client-side AB_DEFAULT_PROMPT (the prompt
actually sent to the LLM) and to normalizeCastingPrompt()'s migration
so already-saved custom prompts pick them up automatically, same
mechanism as the earlier Doppelpunkt-Regel upgrade. Also added
equivalent rules to the server-side fallback prompt in
routes/conversation.py for completeness, though the client-sent prompt
is what's actually used in normal operation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 02:51:46 +02:00
83808a22e1 Fix assign popup being closed before a name-elsewhere click could fill it (v1.14.14)
A document-level "click outside closes the popup" handler only exempted
clicks on the paragraph's speaker label, not clicks anywhere else in the
passage text. Since mousedown fires before click, this closed the popup
(clearing assignModeRow) before the v1.14.13 name-click logic ever ran,
making "click a name elsewhere to feed the open popup" silently do
nothing. Passage-text clicks/drags no longer auto-close the popup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 02:44:10 +02:00
cd20df315d Stop click/drag-on-a-name from reassigning the wrong paragraph (v1.14.13)
The intended flow is: click the broken paragraph's speaker label to open
"Assign to", then supply the name (type, click it anywhere in the text,
or drag-select it). But clicking or drag-selecting a name with no popup
open yet used to open/target a popup for whichever paragraph that name
lived in, silently reassigning THAT paragraph instead of the one the
user meant to fix. Both paths now require an already-open popup before
they do anything — a paragraph is only ever selected via its own
speaker label (or the existing double-click fast-assign shortcut).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 02:33:56 +02:00
3db3616830 Show character avatar images in casting pickers (v1.14.12)
The Characters Found sidebar, the "Assign to" popup's character list,
and the "merge with an already-found character" list now show each
character's saved portrait when one exists (same source as the profile
detail panel), falling back to the existing colour-keyed initial-letter
dot otherwise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 02:09:20 +02:00
2133fe25c7 Stop assign popup from retargeting when reusing a name from another paragraph (v1.14.11)
Clicking a name inside a paragraph's text always retargeted the "Assign
to" popup (and its highlight) to that paragraph, even when the popup was
already open for a different one. That made it look like the wrong
paragraph was being reassigned when the user just wanted to reuse a name
they saw elsewhere. Now, while a popup is open, clicking a name outside
its target paragraph only fills the search box.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 02:02:31 +02:00
f139367f74 Make casting live-view panels resizable, fix collapse chevron direction (v1.14.10)
Passage/Thinking boxes in the audiobook casting live view can now be
resized vertically. Also fixed the character-list collapse button's
chevron pointing opposite to the direction the panel edge actually moves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 01:50:36 +02:00
328e612eb7 Fix empty thinking panels for vLLM builds using a bare "reasoning" key (v1.14.9)
Confirmed by directly probing a streaming response from vllm-0.23.1rc1:
its delta objects carry reasoning text under "reasoning", not the more
common "reasoning_content" key every reasoning-display code path was
checking for. Added a shared _reasoning_text() helper that checks both,
used by the Conversation Playground, audiobook casting's live view, and
character sheets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 01:42:24 +02:00
8d5050bb80 Raise audiobook casting timeouts to 10min/5min for large models (v1.14.8)
Was 6min/3min. 122B+ models can take longer than that just to produce
a first token; 10min also happens to match the server-side cap
(_request_timeout_seconds), so this is the max without raising that too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 00:09:04 +02:00
c969150095 Fix triple-duplicated passage in audiobook casting's live LLM view (v1.14.7)
The "LLM Reading…" split view showed the same passage text three times
(reader pane, Passage column, and a full-width block toggled by the
chevron that duplicated the Passage column). Removed the redundant
full-width block, repurposed the chevron to show/hide the two-column
view itself, and swapped the columns to Passage (left) / Thinking (right).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 23:00:16 +02:00
ed76af4976 Add collapsible thinking panel to Conversation Playground, regroup mic controls (v1.14.6)
Reasoning-model chain-of-thought is now separated from the spoken/displayed
reply on the server (handles both a dedicated reasoning_content field and
inline <think> blocks, including chat templates that inject the opening tag
as a prompt prefix so it never appears in the stream). The client shows it
in a panel collapsed behind a "Thinking" chevron instead of dumping raw
reasoning text into the chat or speaking it aloud via TTS.

Also moved the noise-gate slider out of its own row and next to the mic
button, grouping it with Auto-stop/Hands-free/Live agent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 22:54:20 +02:00
2a2c34fb56 Fix invisible Add Custom engine dialog and a TDZ error on Engines tab (v1.14.5)
Two unclosed <div class="card"> tags in the TTS settings markup left the
add-engine dialog nested inside the TTS panel's DOM subtree, so it
rendered at zero size whenever another Engines sub-tab (e.g. Speech
Recognition) was active. Also fixed a `let` declared after its first
use, which threw a ReferenceError on every Engines-tab page load.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 21:45:53 +02:00
939d33f7cc Fix Apply effects button staying disabled after streaming preview (v1.14.4)
Streaming playback never produces an in-memory blob, but the enable
logic required one. Now enables unconditionally and lazily
re-synthesizes a buffered blob on demand if effects are applied before
one exists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 21:26:23 +02:00
bfd1bfb939 Add routing voice-picker, restore voice filters, fix pedalboard (v1.14.3)
- App Routing's output-voice field gets the same searchable
  avatar-thumbnail dropdown used elsewhere, as a browse button
  alongside the existing free-text input (which must stay editable to
  target vd_ Voice Design presets not in the voice library).

- My Voices table: Gender and Rating filter dropdowns existed in the
  JS (populateLibraryFilters, libraryFilterMatch) but their <select>
  elements had been dropped from the visible layout after an earlier
  redesign, replaced with hidden dead placeholders just to keep the
  code from erroring - and since populateLibraryFilters() early-returns
  if any of the three elements are missing, this silently broke the
  already-visible Language/Type dropdowns too. Restored the real
  elements and removed the hidden scaffold; added new Tag and Group
  dropdowns wired to the same filter state the sidebar chips use.

- Audio effects failing with "pedalboard is not installed" despite
  requirements.txt listing it: the package WAS installed, but its
  native extension (pedalboard_native) links against libatomic.so.1,
  an OS-level shared library missing from the python:3.11-slim-bookworm
  base image. Added libatomic1 to the Dockerfile and rebuilt - verified
  `import pedalboard` now succeeds in the running container.

- Relabeled "edit ID"/"copy ID" to "rename filename"/"copy filename"
  in the voice inspector - the feature already renamed the underlying
  .wav/.meta.json/.reference.txt/picture files via the existing
  /api/voice/rename endpoint, it just wasn't obvious "ID" meant
  "filename."

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 21:05:45 +02:00
46ef73bbae feat: enhance TTS language detection with frequency scoring and explicit overrides 2026-07-07 03:59:25 +02:00
ce3ae79132 Fix streams permanently stuck holding the shared LLM lock (v1.14.2)
Confirmed in production: a streaming attribution/character-sheets
request held _attribution_llm_lock for 11+ minutes, well past its
configured timeout, silently rejecting every subsequent passage as
"Attribution engine busy" and falling back to the non-streaming view -
looking exactly like live-thinking had stopped working, when actually
one earlier request never finished.

Root cause: requests' timeout= on a stream=True call only covers the
connect + first byte, not gaps between later body reads. If the LLM
backend goes silent mid-stream (connection left open, no more chunks),
the blocked socket recv() inside iter_lines() can hang indefinitely.
Since that's a native blocking call, not a Python-level yield point,
neither an in-loop wall-clock check nor GeneratorExit from a
disconnected client can interrupt it - both only take effect at the
next bytecode boundary, which never arrives while blocked in the C
extension.

Added _watchdog_close: a daemon thread that force-closes the upstream
connection if the wrapped block hasn't finished within the configured
timeout. Closing the socket from another thread makes the blocked
recv() raise, unblocking the generator so its normal except/finally
cleanup (including releasing the lock) actually runs. Verified against
the live LLM backend post-restart: stream completes normally with
real token-by-token deltas, and the lock is confirmed free immediately
after.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:57:49 +02:00
6905219567 Fix PDF-parsing progress pill drifting toward the bottom (v1.14.1)
It used position:sticky with a top:40vh offset - sticky's containing
block grows with the document, so on a long PDF the pill drifted
toward the bottom instead of staying a fixed distance from the
viewport's actual visible top. Switched to position:fixed.

Also investigated a "characters all gone" report: verified directly
in the SQLite database that all 47 records for the book are intact,
and a fresh browser session renders them correctly with no errors -
this was a transient/stale-page display issue, not data loss.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 16:14:54 +02:00
960389ef5b Add live-streaming output to Character Sheets generation (v1.14.0)
Character Sheets generation only ever showed a progress bar - no
visible reading/thinking/filling-out, unlike the casting flow which
already streams the LLM's output live. Refactored
/api/character-sheets into shared _charsheets_prepare/_charsheets_parse
helpers (same split used for attribution) and added
/api/character-sheets/stream, proxying the LLM's SSE stream through
the same shared lock used by the other attribution endpoints.

Client: new csGenerateStream (mirrors audiobookAttributeStream) tries
the streaming endpoint first per passage, updating a new "Live output"
panel in the progress dialog with the raw JSON answer as it's written
- itself the "watch it fill out the sheet" experience, since there's
no separate reasoning channel worth hiding it behind here. Falls back
to the blocking endpoint on any stream failure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:28:26 +02:00
f2bdd98a4f Fix invisible icon on the Cast as audiobook button (v1.13.10)
A leftover rule colored the icon accent-blue from when this button
was still .btn-secondary (accent icon on a white background). After
making it the primary blue button in the last release, that same blue
rendered blue-on-blue against the button's own background - invisible.
Confirmed via computed style before/after (blue -> white) rather than
guessing from the screenshot alone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:16:25 +02:00
6a9c827ec9 Scope Recast-selected to only the relevant passages (v1.13.9)
Recasting one character out of a 149-passage book was reading the
entire text every time, exactly as flagged: "you only need to read a
couple of paragraphs before and after his name." csForReaderSelective
now matches the picked character's name + known aliases against the
book's chunks, keeps one chunk of context on either side for
pronoun/"he" resolution, and only extracts from those - falling back
to the full book only if nothing matched at all (e.g. a name typo).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:10:20 +02:00
1b38fce792 Declutter Read Aloud toolbar: drop redundant Character sheets button (v1.13.8)
Character sheet generation is already available from "Cast Characters"
in the Casting flow, so the standalone button here was a duplicate
entry point. Removed it and made "Cast as audiobook" the primary blue
action, moved to the end of the toolbar as the clear next step in the
pipeline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:01:56 +02:00
07eadeff1e Fix Recast-selected running on the full roster, improve dropdown positioning (v1.13.7)
csForReaderSelective passed the ENTIRE roster to csGenerate as the
target list, so the "N cast characters queued" progress dialog showed
every character regardless of what was actually checked in the picker
- only the final save step was correctly filtered, making the whole
run look like it ignored the selection. Now only the picked name(s)
go in as the target roster.

Also fixed the "Recast options" dropdown opening off-screen: its
anchor sits in the bottom action bar, so opening downward (the
default) routinely pushed it past the viewport edge. Opens upward
when there isn't enough room below, with more prominent styling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:35:27 +02:00
95a4c95b90 Fix wasted empty space in Read Aloud tuning panel layout (v1.13.6)
The backend hint box sat beside the 4 tuning rows via grid-row: 1 /
span 4, forcing it to stretch to their combined height no matter how
short its own text was - usually leaving a large empty gap below a
1-3 line hint. Moved it full-width below the rows instead, sized to
its own content, and dropped the now-redundant two-column grid and
mobile-breakpoint overrides that existed only to manage it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 12:16:10 +02:00
ebd18bcc14 Fix merge-freeze with chunked redraw, add sidebar search/sort (v1.13.5)
Merging two characters could block the main thread long enough to
trigger the browser's own "Page Unresponsive" dialog - the merge
itself is a fast array loop, but redrawing the whole feed afterward
(thousands of DOM rows, each running highlightText's regex pass) was
one long synchronous chunk. A plain spinner overlay can't fix that,
since it freezes right along with everything else in the same JS
turn. Split _abMergeCharacters into a fast relabel step plus a new
_abRedrawSegmentsChunked that rebuilds the feed across animation
frames, driving a real progress bar in the busy overlay instead of a
static "please wait".

Also added search + sort (line count / alphabetical) to the Casting
sidebar's character list, matching the Library's character list
controls.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 12:12:36 +02:00
139cb6b85b Fix table-view column misalignment for real, add character-merge via alias popup (v1.13.4)
The earlier table-view fix (display:table-row on <tr>) wasn't the
whole story: display:flex directly on a <td> (Stimme, Tags columns)
also broke its table-cell participation in Chromium, rendering that
cell stacked at the PREVIOUS column's x-position regardless of
table-layout mode - confirmed via direct DOM/rect inspection, not
guesswork. Moved flex layout to inner wrapper divs and switched to
table-layout:fixed with an explicit colgroup so column widths are
never re-negotiated by content again.

Also added actual character merging to the "also known as" alias
popup: picking an existing roster entry (e.g. "Schmied" from Darag's
popup, when the LLM split one person into two roster names) reassigns
every one of its segments to the character you opened the popup from,
with undo support - not just a linked library alias that left the
live cast still showing both as separate people.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 11:58:42 +02:00
cb76a2f237 Fix short quoted-line drag opening Assign popup instead of Split (v1.13.3)
Any selection under 40 chars was treated as "assign this as a
character name," including short quoted lines like "»Henker«." that
the user clearly meant to split into their own Unknown-speaker
segment - guillemets/quotes are never part of a name. Selections
starting with a quote mark now skip the assign-popup hijack and fall
through to the already-visible "Split text to Unknown Speaker" button.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 11:32:06 +02:00
628bd75a82 Fix table-view layout bug, add per-prompt Generate buttons, sort dropdown (v1.13.2)
Table view was rendering as stacked blocks instead of columns: rows
reused .lib-char-card for its event wiring, but that class's
display:flex;flex-direction:column turned every <tr> into a flex
column. Reset to display:table-row and stripped the leaked-in card
chrome.

Split /api/character-generate-prompts into four independent per-field
LLM calls (from two paired calls) and added a `fields` filter, so the
UI can offer one Generate button per prompt box instead of a single
button that always regenerated all four - cheaper, and further
shrinks each response to reduce truncation risk.

Added a Sort dropdown (Role/Alphabet/Lines/Gender/Voice assigned) to
the Characters/Cast list, persisted like the Cards/Table toggle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 11:20:23 +02:00
204bc3a6c6 Add View Characters + recast-all/selected once a book is already cast (v1.13.1)
"Cast Characters" always blindly regenerated the whole cast from
scratch, even for a book already fully cast - no way to just look at
what's there or touch up a handful of characters without redoing
everyone. Once clGetAllByTagOrBook finds existing characters for this
book, the button becomes a split control: View Characters (jump to the
Library overview) plus a dropdown for Recast all or Recast selected...
(checkbox picker that re-scans the book but only saves updates for the
characters checked).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 01:03:30 +02:00
04531b25f8 Turn Script Rehearser's Stage character strip into a sidebar (v1.13.0)
Replaces the horizontal avatar strip above the transport bar with a
sidebar next to the script page, reusing Read Aloud's Casting sidebar
classes (.ab-cv-side/.ab-char-item) directly instead of a separate
look. Same collapse-to-avatars control, now shows each character's
line count, and clicking a character scrolls the script to their
first line. Removed the CSS/HTML this replaces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 00:58:04 +02:00
8922496ec9 Fix silent Design-all crash in Script Rehearser (v1.12.99)
Server logs showed /api/analyze-characters succeeding on every attempt
but /api/voice-design never once being called - the bulk voice-design
loop accessed rehState.cast[sp].voice unguarded right after that
(multi-second, for a large cast) request resolved. Any cast-entry
change during that window threw an uncaught TypeError, silently
killing the whole run with no toast, no further requests, and the
button stuck disabled. Guarded the access and wrapped the remaining
flow in try/catch/finally so failures are visible and the UI always
resets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 23:15:56 +02:00
5fd1660c09 Add character table view, bulk voice-assign, source links, fix prompt truncation (v1.12.98)
Table view: one row per character (avatar, name, sex, line count, voice
language, alignment, voice, tags, prompt-availability checks), toggled
next to the card grid and persisted.

Bulk voice auto-assign: checkbox per character + "Auto-assign selected"
per production, sequential so later picks see what earlier ones just
took (avoids duplicate voice assignments).

Character tags (auto-set to the book of origin) are now visible on
cards - the field always existed, cards just never rendered it, so a
character recurring across books had no visible link between records.

Detail fields (Backstory, Motivation, etc.) now show small numbered
links to their exact source citation when the sheet has one, instead
of making the reader search the full "Quellen im Text" list.

Added gender as an actual extracted character-sheet field - the UI
already had a gender icon but the LLM was never asked for the value.

Fixed SillyTavern/Concept Art prompts still coming back empty despite
the earlier token-budget increase: they're the last two fields in one
JSON object, so truncation always cost the same two regardless of the
ceiling. Split into two independent, concurrent LLM calls instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 23:09:59 +02:00
3fd4f7d052 Fix UTF-8 mojibake, thinking-pane duplication, stale popup closures (v1.12.97)
The streaming attribution endpoint decoded the LLM's SSE response with
requests' guessed encoding (Latin-1 fallback when no charset is declared),
mangling every German umlaut. Forced UTF-8 explicitly.

The "LLM Thinking" pane duplicated the passage text for models that
ignore the <think> instruction and stream straight into JSON - it now
only shows real reasoning when present, and otherwise labels raw output
honestly instead of passing it off as thinking.

Also fixed three UI bugs found while testing a live multi-hour cast:
- A-/A+ font buttons had no effect (a hardcoded font-size on .ab-cv-row
  always overrode the CSS variable they set).
- Typing a name + Enter in the "Assign to" popup (and drag-to-assign,
  which reuses it) silently did nothing after the first cast/recast run
  in a session - the popup is a page-lifetime singleton but its input
  handlers closed over the first run's now-stale assignName/closePopup.
  Every popup open now repoints them at the current run.
- "Split text to Unknown Speaker" split at the wrong spot when the
  selected phrase repeated earlier in the same paragraph (indexOf found
  the first occurrence, not the dragged one). Now uses the exact DOM
  range offset instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 20:18:41 +02:00
6bc98e2b62 Fix ASGI middleware crash and stream/blocking lock race (v1.12.96)
The static-asset caching middleware used BaseHTTPMiddleware, which has a
known Starlette bug: a client disconnecting mid-StreamingResponse (the new
live-attribution SSE stream hitting its idle timeout) raced its internal
task group and raised "RuntimeError: No response returned", crashing that
request. Rewritten as plain ASGI middleware that only touches headers via
the raw send callable, removing the race.

Also found the real cause of the casting timeouts/405s: the streaming
attribution endpoint had its own lock instead of sharing the one the
blocking endpoint already used to serialize on the LLM's single slot -
letting a stream call and its own blocking fallback fire concurrently,
exactly the ghost-request pile-up that lock was built to prevent. Unified
onto one lock and added server-side logging for stream failures.

The "LLM Thinking" pane now shows the model's actual <think> reasoning
instead of the in-progress JSON answer echoed back at the user.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 18:08:54 +02:00
3503f5bda6 Live LLM-thinking stream, fix resolver garbage names, safer rules (v1.12.95)
The LLM Reading card splits into thinking-stream (left) and passage
(right), fed by a new SSE endpoint that shares prompt-building and
parsing with the blocking one and falls back to it on any stream
failure (inactivity timeout, not overall). The deterministic resolver
no longer invents speakers from scenery nouns (PLATZ/GESICHTER/
KLEINIGKEIT) — person-noun whitelist plus a clause-subject pattern —
and gains the impersonal post-quote formula and strict two-person
alternation with colon/page/window guards. All reported failure cases
verified against the exact book sentences.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 10:50:34 +02:00
d1969b7609 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>
2026-07-04 23:17:59 +02:00
42a45c43b6 Resolve Unknown speakers deterministically after each passage (v1.12.93)
The LLM left ~44% of dialogue Unknown even with all deduction rules in
its prompt, so the mechanical ones now run in code per passage: colon
rule (with a non-agent-noun stoplist), post-quote inquit, and the "who
had spoken" pattern. Fills only Unknowns, never overrides the LLM.
Tested against the exact reported failure cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 22:40:14 +02:00
3c3c209d3a Guarantee attribution rules server-side, fix highlight corruption (v1.12.92)
The saved casting prompt was the 2nd-quality verification prompt, so
first-pass attribution ran with the wrong job description and none of
the deduction rules — and the client-side prompt migration had no
anchor to upgrade. The server now appends the rules to any prompt
lacking them, and the saved prompt was reset to the default (backed up
to config/audiobook_prompt.backup.txt). Also: single-pass combined name
regex (a shorter alias could match inside a longer name's data-name
attribute and leak raw style="..." into the feed), stopword filter so a
comma-split alias like "Die, die den Vampir verließ" can't underline
every article, heading-like narration renders bold/centered, and A-/A+
font controls in the casting toolbar sharing the Rehearser Stage scale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 18:19:01 +02:00
c15a91d229 Add Casting back-navigation, fix truncated prompt generation (v1.12.91)
Characters/Cast gains a "Casting" button back to the active casting
session, the pipeline stepper renders on the Library section, and the
stepper's Cast Characters stop navigates instead of side-effect-running
sheet generation. Prompt generation: 4096-token budget (1600 truncated
the four-prompt JSON so two fields silently arrived empty), truncated
answers salvage completed fields, all-empty responses fail loudly, and
partial results name the missing prompts. The PDF-extraction progress
pill is enlarged and vertically centered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 17:43:51 +02:00
e02ca4d703 Fix casting-feed freeze, zip cast export, character generation prompts (v1.12.90)
The per-word <span> wrapping behind "click any word to assign" created
~100k DOM nodes at book scale and froze the tab on every feed redraw;
replaced with native caretRangeFromPoint word detection plus a single
reused hover overlay — same UX, zero extra DOM. Export button gained a
2s re-entry guard (queued clicks during a freeze fired as a download
burst) and now delivers one zip: the cast script in Markdown plus a
sheet per character. Character detail view gains a Generation Prompts
section — four fold-out copy boxes (Voice Design, Character Image,
SillyTavern card, Concept Art sheet) filled by one LLM call over the
full profile via the new /api/character-generate-prompts endpoint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 12:48:00 +02:00
637aeb3a2c Sharpen speaker attribution rules and auto-detect book language (v1.12.86)
The casting prompt now teaches the deduction patterns behind most false
Unknown/Narrator assignments: colon-introduced quotes, post-quote inquit
attribution, pronoun resolution to the last-named matching-gender
character, addressee rule, strict two-person ping-pong, and role names
(Ork, Nachbar) as valid speakers. Saved prompts upgrade in place; the
2nd Quality Run prompt gets the same toolkit. The attribution language
hint falls back to detecting the book's language from its text instead
of relying on a usually-empty dropdown.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 02:36:28 +02:00