tts-voice-creator-clone-and.../static/sections/s-reader.html
mARTin-B78 9d80ec27e7 Merge Reader/Casting/Rehearser pipeline into one guided workflow (v1.12.84)
- Add a 6-stage pipeline stepper (Source -> Cast Audiobook -> Cast Characters
  -> Script Rehearser -> Generate MP3s -> Audiobook) with direct, non-destructive
  jumps between stages and a prominent guided-tour look
- Split PDF import into an explicit "load" then "Extract Text" step, with
  in-browser OCR (Tesseract.js, vendored) to recover chapter headlines baked
  into a PDF as images instead of real text
- Fix casting feed silently merging pages after leaving/returning: segments
  now carry their own page number instead of re-guessing it from text
- Fix excessive "Unknown" speaker attribution: restore the attribution LLM's
  output token budget, which had been cut roughly in half and was truncating
  dialogue-dense passages
- Fix Theater Play library cards failing to open (dead pre-migration
  IndexedDB API calls, missing section navigation)
- Fix bulk "Set tag" wiping a voice's existing tags instead of adding to them
- Start merging Casting's feed with Script Rehearser's Stage UI: collapsible
  character sidebar, shared "paper" page styling, inline text editing
- Fix a performance regression from that merge (per-row listeners on every
  redraw) by moving to event delegation
- Various layout/clutter fixes: hide reader chrome until a document is
  loaded, collapse secondary settings by default, fix overlapping toolbar
  icons, fix duplicate "opening" notifications

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 17:45:38 +02:00

198 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<div class="section-head">
<span class="section-icon"><span class="mdi mdi-book-open-page-variant-outline"></span></span>
<div class="section-title">
<h2>Read Aloud</h2>
<p>Import a PDF or text document, pick a voice and speed, then have it read to you while the word being spoken is highlighted.</p>
</div>
</div>
<div class="wf-stepper" id="wf-stepper-reader" hidden></div>
<div class="tab-content" id="tab-reader">
<div id="reader-main-view">
<!-- ① Import + controls (collapsible) ───────────────────────────── -->
<div class="card reader-config-card" id="reader-config-card" style="padding:14px">
<h2><span class="mdi mdi-tune-variant"></span> Voice &amp; synthesis settings</h2>
<div class="engine-setup-row" style="margin-bottom:8px">
<div class="engine-setup-col" style="flex:1">
<label class="engine-setup-label"><span class="mdi mdi-account-voice"></span> Text to Speech <a href="#" class="engine-setup-link" onclick="navTo('s-llms'); navEnginesCat('tts'); return false;" title="Configure Text to Speech settings"><span class="mdi mdi-cog-outline"></span></a></label>
<div class="engine-setup-controls">
<select id="reader-backend-select" aria-label="TTS backend"><option value="">Checking...</option></select>
<button class="btn-secondary" id="reader-fetch-voices-btn" title="Fetch available voices"><span class="mdi mdi-refresh"></span></button>
<select id="reader-voice-select" aria-label="Voice"><option value="">— fetch voices —</option></select>
</div>
</div>
<div class="engine-setup-col no-border" style="flex:1">
<label class="engine-setup-label"><span class="mdi mdi-tune"></span> Synthesis Options</label>
<div class="engine-setup-controls" style="flex-wrap:nowrap">
<input type="text" id="reader-instruct" placeholder="Optional style / tone" autocomplete="off" style="width:140px">
<select id="reader-chunk-mode" title="Voice consistency" style="width:120px">
<option value="sentence">Per sentence</option>
<option value="paragraph">Per paragraph</option>
<option value="page">Per page</option>
</select>
<label class="reader-ctl reader-norm-toggle" title="Even out loudness" style="margin:0; font-size:11px">
<input type="checkbox" id="reader-normalize" checked> Norm
</label>
</div>
</div>
</div>
<div class="reader-tuning-panel" aria-label="Read Aloud tuning">
<div class="reader-tuning-row">
<label for="reader-speed">Speed <span id="reader-speed-label" class="reader-speed-label">1.0×</span></label>
<input type="range" id="reader-speed" min="0.5" max="2" step="0.05" value="1" aria-label="Reading speed">
<span class="reader-tuning-help">Playback speed while reading.</span>
</div>
<div class="reader-tuning-row">
<label for="reader-seed">Seed</label>
<input type="number" id="reader-seed" placeholder="auto" autocomplete="off">
<span class="reader-tuning-help">Blank uses auto; a fixed value can make repeated synthesis more consistent.</span>
</div>
<div class="reader-tuning-row">
<label for="reader-temp">Temperature</label>
<input type="number" id="reader-temp" min="0" max="1.5" step="0.05" placeholder="default" autocomplete="off">
<span class="reader-tuning-help">Lower values, for example 0.3, keep cloned voices steadier.</span>
</div>
<div class="reader-tuning-row">
<label for="reader-tts-speed">Native speed</label>
<input type="number" id="reader-tts-speed" min="0.5" max="2" step="0.05" value="1" placeholder="1.0" autocomplete="off">
<span class="reader-tuning-help">Backend voice speed before Read Aloud playback control.</span>
</div>
<div class="reader-backend-hint" id="reader-backend-hint"></div>
</div>
<!-- ② Text paste + drag & drop / empty state ────────────────────── -->
<div class="reader-source-panel" id="reader-import-area">
<div class="reader-source-head">
<div>
<strong><span class="mdi mdi-file-document-plus-outline"></span> Source</strong>
<span>Paste text, drop a document, or import a file.</span>
</div>
</div>
<div class="reader-source-grid">
<div class="reader-paste-panel">
<div class="reader-source-method">
<span class="mdi mdi-text-box-outline"></span>
<div><strong>Paste text</strong><span>Plain text, Markdown, chapters, notes, or draft prose.</span></div>
</div>
<textarea id="reader-paste-text" class="reader-paste-text" rows="5" placeholder="Paste text here..." aria-label="Paste text to read aloud"></textarea>
<div class="reader-paste-actions">
<button class="btn-primary btn-sm" id="reader-paste-load" type="button"><span class="mdi mdi-text-box-check-outline"></span> Use pasted text</button>
</div>
</div>
<div class="reader-dropzone" id="reader-dropzone">
<span class="mdi mdi-cloud-upload-outline reader-dropzone-icon"></span>
<strong>Drop a document</strong>
<span class="note">PDF, TXT, or Markdown</span>
<label class="btn-secondary btn-sm reader-file-pick" title="Import a PDF or text file">
<span class="mdi mdi-file-upload-outline"></span> Import file
<input type="file" id="reader-file-input" accept=".pdf,.txt,.md" style="display:none">
</label>
</div>
</div>
</div>
</div>
<!-- ②b Extract Text — shown after a PDF's pages are loaded/visible but before
getTextContent()+OCR has run; this is where extraction options belong ── -->
<div class="reader-extract-banner" id="reader-extract-banner" hidden>
<span class="mdi mdi-text-recognition reader-extract-icon"></span>
<div class="reader-extract-info">
<strong>PDF loaded · <span id="reader-extract-pagecount">0</span> pages</strong>
<p>Extract the text to enable reading, search, and casting.</p>
</div>
<label class="reader-ctl" title="Recover chapter headings that are embedded as images in the PDF (rather than real text) using in-browser OCR. Slightly slows extraction." style="margin:0; font-size:11px">
<input type="checkbox" id="reader-ocr-headings" checked> OCR headings
</label>
<button class="btn-primary btn-sm" id="reader-extract-btn" type="button"><span class="mdi mdi-text-recognition"></span> Extract Text</button>
</div>
<!-- ③ Zoom toolbar + search + status legend (PDF) — above the document ───── -->
<div class="reader-toolbar" id="reader-toolbar" hidden>
<div class="reader-zoom" id="reader-zoom">
<button class="btn-secondary btn-sm" id="reader-zoom-fitw" title="Fit width"><span class="mdi mdi-arrow-expand-horizontal"></span> Fit width</button>
<button class="btn-secondary btn-sm" id="reader-zoom-fith" title="Fit height"><span class="mdi mdi-arrow-expand-vertical"></span> Fit height</button>
<button class="btn-secondary btn-sm" id="reader-zoom-two" title="Two pages side by side"><span class="mdi mdi-book-open-outline"></span> Two pages</button>
<span class="reader-zoom-sep"></span>
<button class="btn-secondary btn-sm" id="reader-zoom-out" title="Zoom out"><span class="mdi mdi-minus"></span></button>
<span class="reader-zoom-pct" id="reader-zoom-pct">100%</span>
<button class="btn-secondary btn-sm" id="reader-zoom-in" title="Zoom in"><span class="mdi mdi-plus"></span></button>
</div>
<div class="reader-search-wrap">
<span class="mdi mdi-magnify reader-search-icon"></span>
<input type="search" id="reader-search" class="reader-search-inp" placeholder="Search…" autocomplete="off" aria-label="Search document text">
<span class="reader-search-status" id="reader-search-status"></span>
<button class="reader-search-btn" id="reader-search-prev" type="button" title="Previous match" aria-label="Previous search match"><span class="mdi mdi-chevron-up"></span></button>
<button class="reader-search-btn" id="reader-search-next" type="button" title="Next match" aria-label="Next search match"><span class="mdi mdi-chevron-down"></span></button>
<button class="reader-search-btn" id="reader-search-clear" type="button" title="Clear search" aria-label="Clear search"><span class="mdi mdi-close"></span></button>
</div>
<div class="reader-legend">
<span class="reader-leg reader-leg-pending">Not synthesised</span>
<span class="reader-leg reader-leg-synth">Synthesising</span>
<span class="reader-leg reader-leg-ready">Ready</span>
<span class="reader-leg reader-leg-reading">Reading</span>
</div>
</div>
<!-- ④ Document viewport — central main content ───────────────────── -->
<div class="reader-doc" id="reader-doc"></div>
<!-- ⑤ Transport — below the document ─────────────────────────────── -->
<div class="reader-transport" id="reader-transport" hidden>
<div class="reader-tb-group">
<button class="reh-tb-btn" id="reader-prev" title="Previous sentence"><span class="mdi mdi-skip-previous"></span></button>
<button class="reh-tb-btn reh-tb-play" id="reader-play" title="Play / pause"><span class="mdi mdi-play"></span></button>
<button class="reh-tb-btn" id="reader-stop" title="Stop"><span class="mdi mdi-stop"></span></button>
<button class="reh-tb-btn" id="reader-next" title="Next sentence"><span class="mdi mdi-skip-next"></span></button>
</div>
<div class="reader-progress-wrap">
<div class="reader-progress-track"><div class="reader-progress-fill" id="reader-progress-fill"></div></div>
<span class="reader-progress-label" id="reader-progress-label">0 / 0</span>
</div>
<span class="reader-doc-title" id="reader-doc-title"></span>
</div>
<!-- ⑥ Synthesise-ahead bar — below the document ──────────────────── -->
<div class="reader-synthbar" id="reader-synthbar" hidden>
<button class="btn-primary btn-sm" id="reader-synth-all" title="Pre-synthesise the chosen range for gap-free reading">
<span class="mdi mdi-lightning-bolt"></span> Synthesise <span id="reader-synth-scope">all</span>
</button>
<span class="reader-page-range" id="reader-page-range" hidden>
<span class="note">pages</span>
<input type="number" id="reader-page-from" min="1" value="1" class="reader-page-inp" aria-label="From page">
<span class="note"></span>
<input type="number" id="reader-page-to" min="1" value="1" class="reader-page-inp" aria-label="To page">
</span>
<button class="btn-secondary btn-sm" id="reader-select-toggle" title="Click sentences to mark a start and an end"><span class="mdi mdi-cursor-default-click-outline" id="reader-select-icon"></span> <span id="reader-select-label">Select range</span></button>
<span class="reader-sel-hint" id="reader-sel-hint" hidden></span>
<span class="reader-sel-info" id="reader-sel-info" hidden>
<span id="reader-sel-text">selection</span>
<button class="reader-sel-clear" id="reader-sel-clear" title="Clear selection"></button>
</span>
<button class="btn-secondary btn-sm" id="reader-save-lib" title="Save this document + its synthesised audio to your library"><span class="mdi mdi-content-save-outline"></span> Save to library</button>
<button class="btn-secondary btn-sm" id="reader-audiobook-btn" title="Let an LLM detect characters &amp; dialogue in this scope, then open it in the Script Rehearser to cast a voice per character"><span class="mdi mdi-drama-masks"></span> Cast as audiobook</button>
<button class="btn-secondary btn-sm" id="reader-charsheets-btn" title="Extract actor-facing RPG-style character sheets (with page + quote sources) from this scope"><span class="mdi mdi-account-details-outline"></span> Character sheets</button>
<span class="reader-export">
<select id="reader-export-mode" class="reader-page-inp" style="width:auto" aria-label="Export granularity" title="Choose how to group the exported MP3 files">
<option value="page">1 MP3 / page</option>
<option value="sentence">1 MP3 / sentence</option>
</select>
<button class="btn-secondary btn-sm" id="reader-export-btn" title="Download the synthesised audio as MP3 (synthesises any missing sentences first)"><span class="mdi mdi-download"></span> Export MP3</button>
</span>
<div class="reader-synth-prog" id="reader-synth-prog" hidden>
<div class="reader-synth-track"><div class="reader-synth-fill" id="reader-synth-fill"></div></div>
<span class="reader-synth-label" id="reader-synth-label">0 / 0</span>
<button class="btn-secondary btn-sm" id="reader-synth-cancel">Cancel</button>
</div>
</div>
</div><!-- /reader-main-view -->
<!-- ⑦ Audiobook Casting Panel ──────────────────────────────────── -->
<div id="reader-audiobook-panel" hidden></div>
<!-- Saved books now live in the combined Library → Books section. -->
</div><!-- /tab-reader -->