Commit Graph

371 Commits

Author SHA1 Message Date
55f147d7a7 fix(magazine): derive figure boundaries from the page render
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
A vision OCR engine grounds a figure far less precisely than it
transcribes one. Over all 138 figure regions of a 204-page issue the
transcribed contents were right while the boxes were routinely offset,
undersized, or sitting on the neighbouring text column -- page 23's card
grid was reported at x 0.24-0.54 where the graphic runs 0.41-0.95.

snap_visual_bboxes() reduces the page render to an ink-coverage grid,
seeds a connected-component search with the engine's box, and re-joins
tiles a gutter apart so a card grid comes back as one figure. Text
blocks are excluded from the mask, which is what stops a figure
swallowing the column beside it. A candidate must be solidly inked or
framed on nearly all of its rows and columns (admitting a white-interior
screenshot), may not shrink below 30% of the seed, and reverts when two
figures land on the same graphic; anything else keeps the box it had.

Runs before the figure crop in both OCR paths, so the images shown
during playback are framed correctly too. Replaces refine_picture_
bounds(), which only recognised coloured panels and had no text fence.

Also adds "Fix figure boundaries" in review and POST
/api/magazine/{id}/figures/refine behind it, so an issue ingested before
this pass can be corrected without re-running OCR. Verified live against
c't KI Wissen 2026: 78 of 138 boxes corrected, the rest declined.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 10:17:00 +02:00
a4b7c18a2f Fix magazine dropzone upload button and finalize side-by-side layout 2026-09-10 17:44:37 +02:00
c1a2d6dc74 Release v1.26.14: Studio-style side-by-side Magazine upload layout with Durchsuchen button
Some checks failed
Test and SonarQube / quality (push) Has been cancelled
2026-09-10 14:45:00 +02:00
30e01a5cec Release v1.26.13: Fix drag-and-drop file opening in tab & improve magazine dropzone reliability 2026-09-10 14:31:16 +02:00
11142842c2 Release v1.26.12: Fix page-wide drag-and-drop interception & magOnShow navigation hook
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-10 13:22:26 +02:00
f5f887bb1d Release v1.26.11: Enhanced layout-aware OCR element recognition, grounding parsing, and preview thumbnails
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-10 13:08:53 +02:00
29dc913ed1 Release v1.26.10
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-10 12:50:33 +02:00
e6211f1333 Release v1.26.9
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-10 12:45:42 +02:00
9b1e29bb79 feat: automate semantic magazine layout
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-09 17:48:56 +02:00
f8af18e8f7 fix: release current magazine UI assets
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-09 17:40:25 +02:00
31b13a4e16 feat: improve magazine review navigation
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-09 17:36:27 +02:00
c1d86ec13d fix: return All magazines to library
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-09 17:14:27 +02:00
272d4872bd fix: use native Unlimited-OCR layout regions
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-09 13:27:14 +02:00
b43bacc275 chore: add local maintenance utilities
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-09 07:53:13 +02:00
037f3dd74d fix: stabilize OCR and character sheet casting
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-09 07:51:59 +02:00
b7aade39db Release v1.26.1
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
2026-09-08 23:39:44 +02:00
85080f4b82 feat: add live ingest preview, region icons/ownership badges, manual region drawing to magazine review
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
Ingestion now streams each page's image and recognised text into a
console-styled preview panel (Baidu Unlimited-OCR demo style) as pages
finish, instead of a bare progress bar -- raw text is written to a
per-job ingest_preview.json file since the background-job result field
strips text-like keys for data minimization.

Split-screen review boxes and their matching list rows now show a
centred icon for the region's kind and, when a region on a shared page
belongs to a different article, a badge naming that article, so a
claimed region is never mistaken for unclaimed text.

Added a "Draw it" mode: click-drag a box directly on the page image to
add a region the layout engine missed, as typed/OCRed text or as a new
figure (add_manual_region, POST /api/magazine/{doc_id}/pages/{page}/regions).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 18:52:21 +02:00
5adba6b4c0 feat: split-screen article review with numbered regions, per-page/whole-magazine "OCR again", and a real column-interleaving fix it found
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
Split-screen review: the rendered page (left) with a numbered box per
detected block, the extracted text (right) with matching numbers.
Clicking either side highlights the other. Checkboxes moved from a flat
list into this layout without changing their semantics (still gate
what gets narrated).

"OCR again": reocr_page() forces one page through the configured
vision engine again, replacing only that page's blocks -- deliberately
does NOT call build_articles(), so an already-approved article is not
silently re-grouped. A magazine-level option re-reads every page and
re-runs detection (with a confirmation, since that does reset article
status). extract_pages() gained force_vision_ocr to skip its quality
gate for this case.

Building the numbered-box review immediately did what it was built to
do -- surfaced a real bug on live production data (real magazine, real
scan, not a synthetic fixture): a photo caption at 49.9% of the page
width sat in the gutter between two genuinely separate columns and
merged them into one, so every block on the page came back column=0
and reading order fell back to pure top-to-bottom, interleaving the
two columns' sentences. Fixed two compounding causes: column boundary
detection now excludes blocks much wider than the page's median width,
and detect_columns' gutter threshold (3.5%) was itself narrower than
some real magazine gutters (~2.1% measured) -- lowered to 1.5%.

10 new/updated tests, including one built directly from the real
measured page-17 block coordinates that exposed the bug.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 14:00:34 +02:00
e81fa64448 fix: magazine state persistence, misfiled in book library, confidence badge layout, docker-socket-proxy crash loop
Magazine "forgets" the open document (real bug, confirmed live):
- A full page reload or the browser's own Back button tears down all
  in-memory JS state; magInit() always defaulted to the empty library
  view with no way back to what was open. Persists the last-opened
  magazine id in localStorage and auto-reopens it on load, falling back
  to the library only if that document no longer exists.
- The actual root cause of the reported duplicate: readerRenderLibrary()
  (Studio/Reader "Source") and libraryRenderBooks() (Library > Books)
  both listed EVERY document from the shared reader_library store with
  no kind filter, so a magazine (kind: "magazine") was already showing
  up there disguised as a plain book. The magazine never actually
  vanished -- it was misfiled in a different list, which is what led to
  the user re-uploading it and getting a real duplicate under a
  different kind. Both listings now exclude kind: "magazine".

Confidence badge layout (Studio Characters review):
- The badge (radiobox is already positioned after the speaker name in
  DOM order) rendered on its own line far below the name because both
  .ab-cv-spk and .ab-cv-txt were `display: block`, forcing a line break
  regardless of markup order. Wrapped the name + badge + check button in
  a shared flex row (.ab-cv-spk-row) so the badge now sits directly
  beside the name/after "NARRATOR" as requested, without needing to
  touch the two existing incremental-patch code paths (they insert
  relative to the same elements, now just nested one level deeper).

docker-socket-proxy crash loop (found while restarting for the magazine
Python changes to take effect -- unrelated to this session's earlier
work, landed in an earlier commit during the interruption):
- read_only: true left the entrypoint with nowhere to write its own
  haproxy.cfg ("Read-only file system"), crash-looping the container and
  leaving the app without Docker access. Removed read_only for this one
  service -- cap_drop: ALL, no-new-privileges, and the :ro socket mount
  already provide the real hardening.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 09:39:20 +02:00
3c210df8a4 fix: magazine dropzone opened the file in the browser instead of accepting the drop
Two real bugs, both confirmed live:

1. dragover's preventDefault() was gated on dataTransfer.types including
   'Files' -- during a real OS file drag, Chromium does not reliably
   populate that list synchronously on every dragover event, so the
   condition failed often enough that preventDefault was silently
   skipped. Per the HTML5 DnD spec, an element only becomes a valid drop
   target once dragover (and, in some browsers, dragenter) has been
   cancelled -- without that, the browser falls through to its native
   "open the dropped file" behaviour, which is exactly what was
   reported. Removed the gate entirely; dragenter is now also handled,
   not just dragover.

2. No page-wide safety net existed, so a drop landing even slightly
   outside the dropzone element bubbled to the browser's default action
   uncontested. Added a window-level dragover/drop preventDefault
   installed once regardless of which section is active.

This also explains the "no hover highlight" symptom: the same
gated-preventDefault bug meant dragover never reliably reached the
class-toggle code either.

Verified live: dragenter/dragover/drop each report defaultPrevented,
the highlight class appears on dragenter and clears on drop, the
dropped file populates the hidden input, and a drop dispatched directly
on <body> (simulating one that misses the zone) is also intercepted by
the new window-level guard.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 00:11:54 +02:00
5b0c8e0bf5 feat: add drag-and-drop dropzone to the magazine upload form
Replaces the plain <input type="file"> with the same reader-dropzone
component the Book/Source screen already uses (reused CSS, no new
styles needed beyond grid/layout adjustments) -- drag a PDF onto the
zone or click "Choose file", both paths route through the same hidden
input magCreate() already reads via a DataTransfer handoff, so no other
logic needed to change. Shows the chosen/dropped filename in place of
the default hint text, and resets it after a successful add.

Verified live: click-to-choose and a simulated real 'drop' DragEvent
both populate the file input correctly, dragover highlighting toggles,
no console errors from the app itself.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 00:03:01 +02:00
813e06c914 feat: complete magazine P1 (images, play-all, karaoke, driving mode, sidebar exclusion) + OCR engines page + second OCR engine
Magazine P1:
- Images shown automatically during playback (assign_images_to_articles
  links each figure to the article whose column it falls in; the client
  shows the last image reached in reading order as playback advances).
- "Play all articles" queues every approved article and advances
  automatically at the end of each rather than stopping.
- Word-level highlighting, explicitly documented as an ESTIMATE (no
  forced-alignment/timestamp source exists): each word's share of the
  sentence's real measured audio duration is proportional to its
  character length. A checkbox turns it off; sentence highlighting
  (measured, not estimated) is unaffected.
- Driving mode: full-screen overlay, two oversized controls, reuses
  whatever is already playing/queued.
- Sidebars/pull-out boxes excluded from narration automatically
  (mark_sidebars: an anomalously narrow column vs. the page's regular
  grid). Fixed a real defect found while building this: a full-width
  headline was left in the column-width analysis and could bridge two
  genuinely separate columns into one, hiding a real sidebar column
  entirely -- caught by a failing test before the fix.

Second OCR engine + fallback ladder:
- KDL Frontier Parser nano wired in via vision_ocr_page_chain (ordered
  engine list, first usable result wins, tesseract last resort).
  Confirmed live: ~3x faster than Baidu (~12s vs ~40s/page) but returns
  plain text with NO coordinate markers, unlike Baidu's labeled regions.
  _whole_page_region treats the whole response as one ambiguous
  full-page block (gated by ocr_quality_score) rather than discarding
  usable text just because it lacks layout structure.

OCR engines page + a real UI bug:
- The OCR & Document Parsing page had no .s-engines-page[data-page="ocr"]
  markup at all -- renderLocalContainers already supported an OCR grid
  generically, but $('dc-grid-ocr') was always null, so the card grid
  silently never rendered. Added an Active OCR Engine panel mirroring
  Active Language Model exactly, plus the Local grid -- both configured
  engines now appear as ready-made cards, no "Add Custom" dialog needed.
- Fixed the audiobook pipeline's "Previous/Next" nav bar bleeding into
  every .section-head in the document (confirmed live on the Engines
  page: a "Cast Audiobook" button and "STEP 1/8" hint that made no sense
  there). Changed from a blocklist (naming each unrelated section as
  noticed) to an allowlist of the pipeline's three actual destinations.

Verified live end-to-end: play-all queue building and manual advance,
word-span markup, driving-mode toggle, OCR page showing both engines as
local cards, zero stray nav bars on the Engines page, no console errors.

20 new backend tests (156 total in the magazine module).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 23:39:34 +02:00
71a398b82f feat: add magazine reader frontend (P1)
Studio -> Magazines: library, contents tree grouped by section with
confidence/pages/listening-time/audio status, the Stage D review
screen (tick/untick layout blocks, correct title/author, approve or
reject), a reflowed article view with click-to-seek sentence
highlighting, a linked original-page view showing the source bounding
box, and a podcast panel (2-3 host pickers, script generation,
turn-by-turn playback).

Wired into the existing lazy-module system (module-manifest.json,
loader.js, nav.js) rather than a separate app. Reuses /api/tts-preview
for both narration and podcast turns -- no new audio pipeline.

Fixes found building this:
- The pane toggle used role="tab" without the full ARIA tabs pattern
  (aria-selected, roving tabindex, arrow-key nav) -- fails WCAG 2.2 AA
  and hid the buttons from role-based tooling. Replaced with a button
  group + aria-pressed, matching the plain-click interaction it
  actually has.
- Print layout cues ("(continued on page 8)") were being sent to TTS
  verbatim -- narration would have spoken the parenthetical aloud.
  Stripped from the narration copy only; layout.json keeps the
  original for the review screen.
- has_podcast now comes from the articles listing so opening an
  article without a generated script skips a doomed GET instead of a
  routine 404 on every open.

Verified against the real API contract with Playwright (library,
review pre-selection, approval, sentence highlighting + PDF-page
linking, podcast approval gate, zero console errors across the flow)
and manually end-to-end against a real ingested fixture, screenshots
included. Full suite: 94 Python tests, 33/34 desktop Playwright tests
(one unrelated pre-existing flaky DOM-count assertion).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 15:00:58 +02:00
6553d5003d fix: preserve custom OCR engine cards 2026-09-05 08:11:57 +02:00
60ef436ce1 feat: add OCR engine profiles 2026-09-04 22:29:11 +02:00
2caa417401 fix: preserve audiobook dialogue boundaries 2026-09-04 15:36:26 +02:00
18e586d06f refactor: scope conversation state 2026-09-04 13:42:02 +02:00
417d7215d2 refactor: scope i18n state 2026-09-04 13:40:07 +02:00
ee4208ec51 refactor: scope cast compatibility modules 2026-09-04 13:38:40 +02:00
542f44fbb8 refactor: scope seed finder 2026-09-04 13:36:55 +02:00
10f618c179 refactor: scope character columns 2026-09-04 13:35:10 +02:00
d2a8dd43b4 perf: tighten character pagination 2026-09-04 13:31:18 +02:00
7938f9ca74 refactor: scope frontend utilities 2026-09-04 13:27:43 +02:00
d055596504 refactor: scope voice inspector 2026-09-04 13:25:13 +02:00
67ee55d3ac refactor: scope character library 2026-09-04 13:23:54 +02:00
73f8f92826 refactor: scope character sheets 2026-09-04 13:21:31 +02:00
e5bd2a149f refactor: scope voice library 2026-09-04 13:19:52 +02:00
1b7d6b2346 refactor: scope generation module 2026-09-04 13:15:30 +02:00
ababdf6741 refactor: scope stt module 2026-09-04 13:13:10 +02:00
c53c47ce2d refactor: scope ai backends 2026-09-04 13:10:51 +02:00
716e601fac refactor: scope integrations module 2026-09-04 13:06:59 +02:00
3fffae9015 refactor: scope combined library 2026-09-04 13:04:46 +02:00
260c5c22a7 refactor: scope engine support 2026-09-04 13:02:29 +02:00
b747553bf6 refactor: scope routing module 2026-09-04 13:00:10 +02:00
28c72c76dd refactor: scope voice sources 2026-09-04 12:57:53 +02:00
d219aec676 refactor: scope voice clone 2026-09-04 12:55:50 +02:00
998a65f2ba refactor: scope tts preview 2026-09-04 12:53:44 +02:00
656d0473d4 refactor: scope settings module 2026-09-04 12:51:29 +02:00
8002ca1d75 refactor: scope character library 2026-09-04 12:49:22 +02:00
0e615d52de refactor: scope studio module 2026-09-04 12:47:30 +02:00
ac55cd88a3 refactor: scope rehearser cast 2026-09-04 12:44:58 +02:00
df95ada578 refactor: scope rehearser voice design 2026-09-04 12:44:08 +02:00
0f3d410260 refactor: scope rehearser voice picker 2026-09-04 12:43:11 +02:00
dea1de5c57 refactor: scope rehearser stage 2026-09-04 12:42:22 +02:00
da3957347c refactor: scope audiobook network 2026-09-04 12:41:35 +02:00
7ee5f65e00 refactor: scope rehearser library 2026-09-04 12:40:56 +02:00
0ee43421b7 refactor: scope reader module 2026-09-04 12:40:09 +02:00
66c5dd6e0c refactor: scope audiobook core 2026-09-04 12:37:37 +02:00
c6db55e55b refactor: scope rehearser core 2026-09-04 12:33:55 +02:00
6c62a0f75e refactor: scope rehearser recording 2026-09-04 12:31:03 +02:00
3a7f0bf677 refactor: scope rehearser io 2026-09-04 12:30:13 +02:00
a202230654 refactor: scope rehearser playback 2026-09-04 12:29:19 +02:00
cc4fc4ae2e refactor: scope audiobook state modules 2026-09-04 12:26:21 +02:00
2aca152fe8 refactor: scope training ui module 2026-09-04 12:23:03 +02:00
302dbe1279 refactor: scope pure frontend leaves 2026-09-04 12:20:33 +02:00
cefd4bd494 refactor: extract rehearser training mode 2026-09-04 12:12:11 +02:00
0c7cc04643 refactor: extract rehearser recording 2026-09-04 12:10:00 +02:00
ba7b3115b9 refactor: extract audiobook preview export 2026-09-04 12:05:50 +02:00
76421dfe88 refactor: extract audiobook casting pipeline 2026-09-04 12:04:05 +02:00
eaf2f29d62 refactor: extract rehearser io 2026-09-04 12:01:38 +02:00
f97fd60e43 refactor: extract rehearser playback 2026-09-04 11:59:09 +02:00
e096b3e529 refactor: wire audiobook namespace APIs 2026-09-04 11:54:56 +02:00
6f2c0b7b3d refactor: extract audiobook cast view 2026-09-04 11:53:04 +02:00
262e37fffd refactor: extract rehearser voice matching 2026-09-04 11:47:51 +02:00
eb008475ed refactor: extract rehearser cast renderer 2026-09-04 11:39:45 +02:00
ae9df4f7f0 feat: distinguish provider configuration and health 2026-09-04 11:35:52 +02:00
7c14f41cb9 refactor: extract audiobook speaker normalization 2026-09-04 11:32:20 +02:00
0d1299b19c refactor: extract audiobook cast cleanup 2026-09-04 11:30:12 +02:00
1951ce0646 refactor: extract audiobook dialogue merging 2026-09-04 11:27:49 +02:00
41349867a1 refactor: extract audiobook cast normalization 2026-09-04 11:25:52 +02:00
0876527554 refactor: extract audiobook quote segmentation 2026-09-04 11:22:15 +02:00
2fde0a176d refactor: extract rehearser voice design 2026-09-04 11:19:01 +02:00
c0ac5dd775 refactor: extract audiobook cast serialization 2026-09-04 11:08:49 +02:00
d3bc806f33 refactor: extract rehearser training comparison 2026-09-04 11:06:23 +02:00
d613591526 refactor: extract rehearser cast controls 2026-09-04 11:04:13 +02:00
ed3b2d42cd refactor: publish feature APIs through TTVC 2026-09-04 11:00:22 +02:00
66ebd1a8b8 refactor: extract audiobook parsing primitives 2026-09-04 10:56:31 +02:00
0bfb6d694b refactor: extract rehearser voice picker 2026-09-04 10:54:21 +02:00
e98c5ffe33 refactor: extract rehearser stage controls 2026-09-04 10:52:05 +02:00
2712843322 refactor: extract rehearser library persistence 2026-09-04 10:46:02 +02:00
c815b7941a feat: expand resource diagnostics 2026-09-04 10:42:08 +02:00
8d7511cdbf test: measure constrained character workloads 2026-09-04 10:34:27 +02:00
4b01a853fb perf: bound browser library image cache 2026-09-04 10:28:07 +02:00
0f1d93c9df perf: bound rehearser audio cache 2026-09-04 10:11:44 +02:00
bf186387f9 perf: coalesce voice library fetches 2026-09-04 10:10:10 +02:00
55d0dd1012 refactor: add frontend namespace migration bridge 2026-09-04 10:07:46 +02:00
aa8a96c48f feat: add safe retries for background jobs 2026-09-04 09:58:45 +02:00
6ca044ef64 feat: validate canonical image asset digests 2026-09-04 09:53:50 +02:00
90e74149d9 feat: add constrained-device resource diagnostics 2026-09-04 09:51:49 +02:00
690673625d feat: persist background job result artifacts 2026-09-04 09:48:37 +02:00
b648bbdf0b feat: add safe background job history panel 2026-09-04 09:44:31 +02:00
0fd39d4acb test: cover profile completeness counting 2026-09-04 09:33:45 +02:00
a7a6bb1cdc feat: add visual pair review workflow 2026-09-04 09:20:27 +02:00
d03c9841e9 test: cover constrained browser concurrency 2026-09-04 08:59:09 +02:00
fbb5fc52dd fix: stabilize async library navigation 2026-09-04 08:58:01 +02:00
3456b5a3b0 feat: support character appearance forms 2026-09-04 08:51:33 +02:00
0b51a76c21 fix: flag inconsistent current image pairs 2026-09-04 08:39:01 +02:00
cb653d15d0 perf: reduce library pages on low memory devices 2026-09-04 08:35:18 +02:00
4ffe6e9012 feat: add consistent image job cancellation 2026-09-04 08:34:35 +02:00
47d6a5d247 fix: release local preview object URLs 2026-09-04 08:11:32 +02:00
e52d462dac perf: paginate large voice libraries 2026-09-04 08:05:09 +02:00
b5d2e773d8 perf: paginate large character productions 2026-09-04 08:01:29 +02:00
9517cca00c feat: detect stale character image identity 2026-09-04 07:59:17 +02:00
94f09c110d feat: version character visual identity 2026-09-04 07:58:16 +02:00
c6ce3e4803 security: keep provider credentials server-side 2026-09-04 07:50:30 +02:00
a8f8779736 Release v1.22.3 2026-09-04 07:28:26 +02:00
fe807d1682 Release v1.22.2 2026-09-03 16:12:02 +02:00
c9d063d0c7 Fix character profile view missing fields (Race, Age, Languages, Nationality, Class) 2026-08-23 12:18:08 +02:00
882e1ede55 Fix audiobook export modal bindings and position for SPA routing 2026-08-23 11:58:36 +02:00
ee17ed97d1 Fix audiobook export modal visibility and eliminate 4-10s TTS delay for narrator blocks 2026-08-23 11:45:20 +02:00
1d8e0ff2a6 feat: Audiobook Export Modal, UI sync fixes, and initialization stability (v1.22.0) 2026-08-23 10:15:46 +02:00
f7264a8c07 chore: release 1.21.0 - Consistent ComfyUI Character Renders and Audiobook Export
- Deterministic hash-based character portraits and reference sheets
- Implemented strict early Renaissance aesthetic controls and role-specific headwear rules
- Added audiobook batch export scripts and progress tooling
- Verified book profiles with a new 'confirmed' state tracking mechanism
- Updated Pillow dependency for robust image handling
- Configured .gitignore for audiobook and audio cache exports
2026-08-23 09:12:57 +02:00
2d5ecea53a Release completed voice preview blob URLs 2026-08-19 22:05:31 +02:00
08a6544661 Lazy load character artwork 2026-08-19 17:12:45 +02:00
aa15265021 Show generated voice file and preview in character profiles 2026-08-19 17:12:02 +02:00
8cc92ca02b Add low-memory client concurrency guard 2026-08-19 17:01:06 +02:00
b953ba1a83 Refactor table and audiobook helper modules 2026-08-19 16:39:17 +02:00
388fd601ab Release v1.20.38 2026-08-19 16:36:26 +02:00
189ca51e47 Release v1.20.37 2026-08-19 16:04:30 +02:00
62274a17fc Improve Studio casting and character voice design (v1.20.36) 2026-08-15 13:20:38 +02:00
35cb21ad70 Reject phantom speakers invented from German noun phrases (v1.20.35)
The caster emits fragments of noun phrases as characters — "in seinem Inneren"
-> Inneren, "die Hand" -> Hand, "mein Geliebter" -> Geliebten, "der Wind" ->
Wind. Each became a cast entry with its own voice while the real speaker's line
was lost to a phantom; measured against a hand-corrected book this was a large
part of why a fresh cast produced 73 speakers where the human had 62.

Such lines are now reset to Unknown — visibly unresolved and still recoverable
by the repair passes, rather than silently wrong. Applied to a real cast:
5 phantoms removed, 73 -> 68 speakers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 16:52:27 +02:00
4d3e4f8e7c Consolidate speaker labels after recast passes too, not only after casting (v1.20.34)
Consolidation ran only at cast completion, but recast/repair passes assign
speakers of their own and can re-introduce the variants just removed —
confirmed live: a freshly cast book gained "Alrik" alongside "Alrik von
Blautann" during auto-repair, splitting one character across two voices again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 14:57:56 +02:00
cd5c325265 Consolidate speaker label variants after casting (v1.20.33)
One character is referred to by several labels across a book — bare vs full
name, a title on its own, a stray fragment — and each variant became its own
cast entry with its own voice, so the character audibly changed voice
mid-scene. Measured against a hand-corrected book this accounted for more of
the disagreements than genuine misattributions.

Variants are folded together only when one label's words are a strict subset
of another's after stripping articles and titles, so "Weber" and "Alter Weber"
stay separate and unrelated names never merge. Applied to a real cast:
Sharraz -> Sharraz Garthai, Baronin -> Baronin Ira von Seewiesen,
Von -> Oberst Alrik von Blautann (40 lines, 58 -> 54 speakers).

Also moves the quotation-mark toggle into the A- / A+ toolbar row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 13:34:40 +02:00
f837ba1a4f Toggle quotation marks with CSS instead of rewriting text (v1.20.31)
CSS cannot target a character inside a text node, so the marks are now wrapped
in their own .ab-q element at render time and hidden with a single class on
body. Toggling is instant regardless of segment count, and the stored text is
never modified.

Also fixes the toggle looking inert on a freshly cast book: it previously only
added missing marks and never hid the ones the book already carried. Button
moved bottom-right as a larger labelled pill, clear of the search controls.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 13:14:35 +02:00
55f634e626 Show quotation marks in the casting script, with a toggle (v1.20.29)
German prose never quotes narration, so displaying the guillemets makes a
mislabeled line obvious at a glance. Extraction strips them from ~80% of
segments, so they are rendered rather than stored: dialogue rows display
wrapped in » «, lines that kept their own marks are left as-is, and the saved
text is untouched so synthesis and exports are unaffected. A toolbar button
toggles visibility and the choice persists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 10:51:46 +02:00
ca654608ac Never merge adjacent Unknown dialogue lines into one segment (v1.20.27)
Adjacent segments merge when they share a speaker, but an unattributed line
carries the speaker "Unknown", so two consecutive Unknown lines compared as
equal and were fused into a single block joined by a blank line. Confirmed
against the source text: Lysandra's line and Perdia's interruption — two
separate paragraphs in the book — became one segment, after which no
attribution pass could separate them and one voice would read both sides of
the exchange.

"Unknown" means the speaker is not known, never that two lines share one.
Narration and lines with a real speaker name merge exactly as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 10:46:55 +02:00
14ed0c89e7 Do not auto-attribute segments that fuse several speakers' turns (v1.20.26)
A trailing inquit names only the last speaker, so applying it to a block
holding multiple turns silently mis-credited the earlier one — a wrong
attribution is invisible where an Unknown is not. Such blocks are now skipped
by the grammar rules and left for the LLM pass or a manual split.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 10:44:34 +02:00
4d6eb7bd78 Attribute quotes from a following "X's Stimme" narration (v1.20.25)
"Darrags Stimme klang tonlos." after a quote names who just spoke, but the
voice-announcement rule only looked at the narration before a line, so this
mirror image stayed Unknown. Verified on the book (-> Darrag).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 10:43:12 +02:00
d3b162f81f Extend German inquit attribution: missing verbs, subject choice, mistyped inquits (v1.20.24)
Driven by unresolved lines in a real German novel:

- AB_SPEECH_VERBS was missing many common inquit verbs (grunzte, versetzte,
  unterbrach, drängte, herrschte, keuchte, erklärte, widersprach, warf ein …)
  and had no thought verbs at all, so inner monologue ("dachte Lysandra") was
  never attributed.
- A trailing inquit naming two people ("unterbrach die blonde Perdia Lysandra")
  resolved to the wrong one; the grammatical subject speaks, so articles and
  adjectives are skipped and the first name after the verb wins.
- A quote whose own inquit had been mistyped as dialogue could never resolve,
  because the rule required the following segment to be narration. A segment
  opening with a lower-case speech verb is an inquit whatever its type; it is
  now accepted and its type corrected.

Verified on the book: resolves further lines with no LLM call.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 10:41:06 +02:00
3434293375 Prefer the newest casting draft instead of always trusting localStorage (v1.20.22)
Opening a book always restored the localStorage draft and then pushed it to the
server as a backfill, so a tab holding an older copy silently overwrote newer
work saved from anywhere else — confirmed live: a cast improved from 79 Unknown
lines to 25 was destroyed by opening the book in a stale tab. Local and server
drafts are now compared by savedAt, the newest wins and is cached locally, and
the server is only backfilled when the local copy is actually newer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 10:29:53 +02:00
63ec69ad89 Resolve pronoun-inquit split quotes, use verification prompt for repair pass (v1.20.21)
Two gaps found by validating the repair chain against a real German novel:

Split quotes whose inquit uses a pronoun rather than a name (»Ja, ungewöhnlich«
+ ", antwortete er knapp.") stayed Unknown, because every existing rule
requires a literal name after the speech verb. The antecedent is unambiguous,
so it now resolves the same way the colon rule does — to the last character
named in the narration immediately before the quote. Verified on the book: 2
further lines resolved with no LLM call, including the reported case.

The repairNarration pass also only received the verification prompt when
launched from the menu button; called from auto-repair or programmatically it
fell back to the general casting prompt, which is not written for the "is this
narration actually speech?" question. It now defaults to the verification
prompt in every entry path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 10:23:15 +02:00
30bac15b6e Open toolbar flyout menus downwards when there is room (v1.20.18)
The Identify/Cast flyouts were hard-anchored to open above their trigger,
which suited the casting panel's bottom footer but clipped them off the top of
the screen in Studio, where the same toolbar sits near the top — the first two
entries were unreachable. They now pick the side with space, prefer opening
downwards, stay clamped inside the viewport and scroll if taller than it.

Also reorders the Identify menu to the sequence the passes should run in, with
the one-click Auto-repair cast first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 08:47:17 +02:00
4c686165b3 Add targeted dialogue-repair pass and auto-repair chain, fix rollback on stopped runs (v1.20.16)
Spoken lines that PDF extraction typed as narration were invisible to
"Identify unknown characters" (it only scans type==='dialogue'), so no number
of re-runs could fix them, while the pass that could re-sent all ~1000
narration segments to the LLM. Adds a detector that scores narration for
direct-speech signals and re-checks only those: 36 candidates instead of 1036
on a real novel. Adds "Auto-repair cast" to chain the passes in the right
order automatically after casting.

Also fixes a rollback gap where stopping a run skipped the safety net that
undoes a pass which increased Unknown speakers (confirmed live: 80 -> 159
with no warning), corrects an attribution rule that taught the model the
listener is the speaker, and stops the threshold loop from re-verifying every
narration segment each round.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 08:40:38 +02:00
193cbb202d Fix Fish-Speech 0-voices bug, add inline emotion-tag reference to Try It Out (v1.20.8)
Fish-Speech clones from the app's own saved WAV library but was missing from
the set of backends populated with those voices, so the UI probed the Fish
server for a voice-listing endpoint it does not have, showed "Fetched 0
voices" and left the engine unusable despite being healthy. It now reports
the full library (218 voices).

Also adds a Fish-only panel listing all 49 documented emotion tags with
click-to-insert and a multi-emotion example, and corrects the backend's
advertised capabilities: it was flagged style_aware with "emotion markers are
honoured per request", but measurement shows a reference clip's in-context
prosody overwhelms inline tags (10x loudness spread across emotions without a
reference, 1.2x with one). It is now described by what it does well:
deterministic, byte-reproducible cloning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 00:08:36 +02:00
003e4f9f46 Fix bundle-breaking TDZ throw, use English emotion instructs, forward Fish gen params (v1.20.6)
The emotion quick-pickers added in 1.20.5 guarded with
`typeof REH_EMOTIONS === 'undefined'`, but REH_EMOTIONS is a const declared
later in the bundle's single shared scope — `typeof` on a const in its
temporal dead zone throws instead of returning "undefined", which aborted
top-level initialization for every module bundled after tts-preview.js.
The pickers now read window.REH_EMOTIONS on a deferred macrotask.

Also: emotion instructions are now always built in English (spoken text and
the native-accent clause stay in the book's language), which controlled A/B
testing showed produces a far cleaner prosodic gradient from Qwen3-TTS; and
Fish-Speech now receives temperature/top_p/repetition_penalty, which it was
the only backend never to have forwarded.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 22:00:56 +02:00
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