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>
This commit is contained in:
mARTin-B78 2026-07-08 23:00:16 +02:00
parent ed76af4976
commit c969150095
5 changed files with 36 additions and 36 deletions

View File

@ -9,6 +9,13 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
---
## [1.14.7] — 2026-07-08
### Fixed
- **Audiobook casting's "LLM Reading…" live view showed the passage three times** — once in the reader pane above it, once in a "Passage" column, and once more in a full-width block toggled by the chevron (which duplicated the "Passage" column exactly). Removed the redundant full-width block; the chevron now shows/hides the two-column view itself (previously it appeared automatically once the LLM's first token arrived and had no way to be hidden again). Swapped the column order to Passage (left) / Thinking (right) to match the Conversation Playground's layout.
---
## [1.14.6] — 2026-07-08
### Added

View File

@ -1 +1 @@
1.14.6
1.14.7

View File

@ -10,7 +10,7 @@
<meta name="format-detection" content="telephone=no">
<meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#2563EB">
<meta name="app-version" content="1.14.6">
<meta name="app-version" content="1.14.7">
<link rel="manifest" href="/manifest.webmanifest">
<link rel="icon" href="/static/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/icon.svg">
@ -27,7 +27,7 @@
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css">
<link rel="stylesheet" href="/static/style.css?v=1.14.6">
<link rel="stylesheet" href="/static/style.css?v=1.14.7">
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
@ -365,7 +365,7 @@ window.toggleNavTree = function(treeId, chevronId) {
</script>
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
<script src="/static/loader.js?v=1.14.6"></script>
<script src="/static/loader.js?v=1.14.7"></script>
</body>
</html>

View File

@ -3051,64 +3051,57 @@ STRIKTE FORMAT- UND TEXTREGELN:
this._procRow.className = 'ab-cv-row is-processing ab-cv-llm-row';
const isLong = text.length > 160;
const preview = escHtml(text.slice(0, 160)) + (isLong ? '…' : '');
// Split view while the LLM works: left = its live thinking stream (fed
// by view.thinking via the SSE endpoint), right = the passage it reads.
// Split view (collapsed by default, toggled via the chevron): left =
// the passage being read, right = the LLM's live thinking/raw-output
// stream (fed by view.thinking via the SSE endpoint).
this._procRow.innerHTML = `
<div class="ab-cv-llm-head">
<span class="ab-cv-spk" style="color:var(--blue)"><span class="mdi mdi-loading mdi-spin"></span> LLM Reading</span>
${isLong ? '<button class="ab-cv-expand-btn" title="Show full passage"><span class="mdi mdi-chevron-down"></span></button>' : ''}
<button class="ab-cv-expand-btn" type="button" title="Show passage &amp; thinking" aria-expanded="false"><span class="mdi mdi-chevron-right"></span></button>
</div>
<div class="ab-cv-llm-preview">${preview}</div>
<div class="ab-cv-llm-split" hidden>
<div class="ab-cv-llm-col">
<div class="ab-cv-llm-col-label ab-cv-think-label"><span class="mdi mdi-head-dots-horizontal-outline"></span> LLM Thinking</div>
<pre class="ab-cv-think"></pre>
</div>
<div class="ab-cv-llm-col">
<div class="ab-cv-llm-col-label"><span class="mdi mdi-text-long"></span> Passage</div>
<pre class="ab-cv-llm-pre">${escHtml(text)}</pre>
</div>
<div class="ab-cv-llm-col">
<div class="ab-cv-llm-col-label ab-cv-think-label"><span class="mdi mdi-head-dots-horizontal-outline"></span> LLM Thinking</div>
<pre class="ab-cv-think"></pre>
</div>
</div>
${isLong ? `<div class="ab-cv-llm-full" hidden><pre class="ab-cv-llm-pre">${escHtml(text)}</pre></div>` : ''}
`;
if (isLong) {
this._procRow.querySelector('.ab-cv-expand-btn').addEventListener('click', function () {
const full = this.closest('.ab-cv-llm-row').querySelector('.ab-cv-llm-full');
full.hidden = !full.hidden;
const split = this.closest('.ab-cv-llm-row').querySelector('.ab-cv-llm-split');
const open = this.getAttribute('aria-expanded') === 'true';
this.setAttribute('aria-expanded', String(!open));
split.hidden = open;
const icon = this.querySelector('span');
if (icon) icon.className = full.hidden ? 'mdi mdi-chevron-down' : 'mdi mdi-chevron-up';
if (icon) icon.className = open ? 'mdi mdi-chevron-right' : 'mdi mdi-chevron-down';
});
}
_abPage().appendChild(this._procRow);
trim();
},
// Live LLM output for the passage currently processing. First delta swaps
// the plain preview for the split thinking/passage view; subsequent deltas
// append and keep the thinking pane scrolled to the newest text.
// Live LLM output for the passage currently processing. Streams into the
// (collapsed-by-default) thinking column; the user reveals it via the
// chevron button set up in processing() above.
thinking(delta) {
if (!this._procRow || !delta || this._thinkDone) return;
this._thinkRaw = (this._thinkRaw || '') + delta;
const split = this._procRow.querySelector('.ab-cv-llm-split');
const think = this._procRow.querySelector('.ab-cv-think');
const label = this._procRow.querySelector('.ab-cv-think-label');
if (!split || !think) return;
if (split.hidden) {
split.hidden = false;
const prev = this._procRow.querySelector('.ab-cv-llm-preview');
if (prev) prev.hidden = true;
}
if (!think) return;
// Some models wrap real reasoning in <think>...</think>; others ignore
// that instruction and stream straight into the JSON answer. Show
// whichever is actually arriving — live progress beats nothing — but
// label it honestly instead of calling raw JSON "thinking".
const openAt = this._thinkRaw.indexOf('<think>');
let shown, isReal = false;
let shown, isReal = false, closeAt = -1;
if (openAt >= 0) {
const afterOpen = this._thinkRaw.slice(openAt + '<think>'.length);
const closeAt = afterOpen.indexOf('</think>');
closeAt = afterOpen.indexOf('</think>');
shown = closeAt >= 0 ? afterOpen.slice(0, closeAt) : afterOpen;
isReal = true;
if (closeAt >= 0) this._thinkDone = true;
} else {
shown = this._thinkRaw;
}

View File

@ -5375,7 +5375,6 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
font-size: 12px; color: var(--subtext); font-style: italic;
padding: 2px 0 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ab-cv-llm-full { margin-top: 6px; }
.ab-cv-llm-pre {
font-family: inherit; font-size: 12px; color: var(--text);
white-space: pre-wrap; word-break: break-word;
@ -5403,8 +5402,9 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
/* Every word in the narration/dialogue text is individually clickable the
basis for "click a name in the text to assign", including names the app
doesn't already know (drag across a multi-word one to select it all). */
/* Live LLM view while a passage is processing: thinking stream left, the
passage it reads right. Swapped in by view.thinking() on the first delta. */
/* Live LLM view while a passage is processing: passage left, the LLM's live
thinking/raw-output stream right. Collapsed by default; toggled by the
chevron button in .ab-cv-llm-head. */
.ab-cv-llm-split { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 6px; }
.ab-cv-llm-col { min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.ab-cv-llm-col-label { font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--subtext); display: flex; align-items: center; gap: 5px; }