tts-voice-creator-clone-and.../static/sections/s-magazine.html
mARTin-B78 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

157 lines
8.2 KiB
HTML

<div class="section-head">
<span class="section-icon"><span class="mdi mdi-newspaper-variant-outline"></span></span>
<div class="section-title">
<h2>Magazines</h2>
<p>Listen to a magazine one article at a time &mdash; from its true beginning to its true end, without adverts, sidebars or a neighbouring piece spliced into the middle.</p>
</div>
</div>
<!-- ── Library ──────────────────────────────────────────────────────── -->
<div id="mag-view-library">
<div class="card mag-new">
<h3>Add a magazine or paper</h3>
<div class="mag-new-grid">
<label for="mag-new-title">Title
<input id="mag-new-title" type="text" placeholder="Scientific American" autocomplete="off">
</label>
<label for="mag-new-issue">Issue
<input id="mag-new-issue" type="text" placeholder="September 2026" autocomplete="off">
</label>
<label for="mag-new-file">PDF
<input id="mag-new-file" type="file" accept="application/pdf">
</label>
<button type="button" class="btn primary" onclick="magCreate()">
<span class="mdi mdi-plus"></span> Add magazine
</button>
</div>
<p class="muted">Scanned issues work too &mdash; pages without selectable text are read with OCR.</p>
</div>
<h3>Your magazines</h3>
<div id="mag-library-list" class="mag-grid"></div>
</div>
<!-- ── Contents ─────────────────────────────────────────────────────── -->
<div id="mag-view-contents" hidden>
<div class="mag-bar">
<button type="button" class="btn-sm" onclick="magShow('library')">
<span class="mdi mdi-arrow-left"></span> All magazines
</button>
<h3 id="mag-doc-title">Magazine</h3>
<div class="mag-bar-actions">
<button type="button" class="btn-sm primary" onclick="magPlayAll()">
<span class="mdi mdi-playlist-play"></span> Play all articles
</button>
<button type="button" class="btn-sm" onclick="magIngest()">
<span class="mdi mdi-refresh"></span> Re-run detection
</button>
</div>
</div>
<div id="mag-ingest-status" class="mag-status" hidden></div>
<div id="mag-contents"></div>
</div>
<!-- ── Review (Stage D) ─────────────────────────────────────────────── -->
<div id="mag-view-review" hidden>
<div class="mag-bar">
<button type="button" class="btn-sm" onclick="magBackToContents()">
<span class="mdi mdi-arrow-left"></span> Contents
</button>
<h3>Review article boundaries</h3>
</div>
<div id="mag-review"></div>
</div>
<!-- ── Reading ──────────────────────────────────────────────────────── -->
<div id="mag-view-read" hidden>
<div class="mag-bar">
<button type="button" class="btn-sm" onclick="magBackToContents()">
<span class="mdi mdi-arrow-left"></span> Contents
</button>
<div class="mag-read-id">
<h3 id="mag-read-title">Article</h3>
<span id="mag-read-by" class="muted"></span>
<span id="mag-read-pages" class="muted"></span>
</div>
<button type="button" class="btn-sm" onclick="magToggleDriving()" title="Large controls for listening without looking at the screen">
<span class="mdi mdi-car-outline"></span> Driving mode
</button>
</div>
<div class="mag-controls">
<label for="mag-voice" class="mag-voice-pick">Narrator
<select id="mag-voice"></select>
</label>
<button type="button" class="btn primary" id="mag-play" onclick="magPlay()"
aria-label="Play narration">
<span class="mdi mdi-play"></span> Listen
</button>
<span id="mag-read-progress" class="muted" aria-live="polite"></span>
<span id="mag-queue-progress" class="muted" aria-live="polite"></span>
<label class="mag-karaoke-toggle" title="Highlight each word approximately as it is spoken">
<input type="checkbox" id="mag-karaoke-cb" checked onchange="TTVC.magazine.karaoke = this.checked">
Word highlighting
</label>
<div class="mag-pane-tabs" role="group" aria-label="Reading view">
<button type="button" class="btn-sm active" data-mag-pane="reflow"
aria-pressed="true" onclick="magSetPane('reflow')">Article</button>
<button type="button" class="btn-sm" data-mag-pane="pdf"
aria-pressed="false" onclick="magSetPane('pdf')">Original page</button>
</div>
</div>
<div id="mag-pane-reflow" class="mag-pane">
<div id="mag-read-image" class="mag-read-image" hidden></div>
<div id="mag-article" class="mag-article"></div>
</div>
<div id="mag-pane-pdf" class="mag-pane" hidden>
<div class="mag-page-wrap">
<img id="mag-page-img" alt="">
<div id="mag-page-box" class="mag-page-box" hidden></div>
</div>
<p id="mag-page-label" class="muted"></p>
</div>
<!-- Podcast mode -->
<details class="card mag-podcast">
<summary><span class="mdi mdi-podcast"></span> Podcast mode &mdash; discuss this article</summary>
<p class="muted">Two or three hosts discuss the article for someone who has not read it. The script is written by the configured LLM from the article text; it will not invent facts, but it is a generated discussion, not the author&rsquo;s words.</p>
<div class="mag-hosts">
<label for="mag-host-a">Host A<select id="mag-host-a"></select></label>
<label for="mag-host-b">Host B<select id="mag-host-b"></select></label>
<label for="mag-host-c">Host C <span class="muted">(optional)</span><select id="mag-host-c"></select></label>
</div>
<div class="mag-podcast-actions">
<button type="button" class="btn" id="mag-podcast-gen" onclick="magGeneratePodcast()">Generate script</button>
<button type="button" class="btn primary" id="mag-podcast-play" onclick="magPlayPodcast()" hidden>Play podcast</button>
</div>
<div id="mag-podcast-script"></div>
</details>
</div>
<!-- ── Driving mode overlay ─────────────────────────────────────────── -->
<!-- Large controls, minimal text, no interaction required beyond play/
pause/skip -- reuses whatever is already playing rather than its
own playback path. -->
<div id="mag-driving" class="mag-driving" hidden role="dialog" aria-label="Driving mode">
<button type="button" class="mag-driving-exit" onclick="magToggleDriving()" aria-label="Exit driving mode">
<span class="mdi mdi-close"></span>
</button>
<div class="mag-driving-body">
<h2 id="mag-driving-title">Article</h2>
<p id="mag-driving-by" class="muted"></p>
<div id="mag-driving-queue" class="mag-driving-queue" hidden>
<span id="mag-queue-progress-2" class="muted"></span>
</div>
<div class="mag-driving-controls">
<button type="button" class="mag-driving-btn" onclick="magQueueNext()" aria-label="Next article" title="Next article (only in Play all)">
<span class="mdi mdi-skip-next"></span>
</button>
<button type="button" class="mag-driving-btn mag-driving-play" id="mag-driving-play" onclick="magPlay()" aria-label="Play">
<span class="mdi mdi-play"></span>
</button>
</div>
</div>
</div>