After a successful /api/voices fetch, results are written to sessionStorage
(key ttsvc_vc). On the next page load loadVoiceLibrary() reads the cache and
renders voices immediately before the network request completes, eliminating
the blank-list / skeleton flash entirely.
The background fetch always runs and overwrites the cache with fresh data.
Error handling is graceful: if the network fails but cache was served, the
error is suppressed (stale data stays visible). The Refresh button clears the
cache first to force a full reload cycle.
Silent-refresh logic (from v1.12.19) prevents the second loadVoiceLibrary()
call (engines.js batch E) from blanking already-rendered voices.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause 1 (v1.12.18): `window.loadVoiceLibrary = () => loadVoiceLibrary()`
overwrites the global binding the arrow function references, causing immediate
RangeError: Maximum call stack size exceeded on every call. Changed to direct
assignment `window.loadVoiceLibrary = loadVoiceLibrary`.
Root cause 2 (v1.12.18): `loadSettings()` called `renderSettingsAbout()` which
lives in conversation.js (batch E), loaded after init.js. Guard added with
typeof check; nav.js already calls it safely when the About section opens.
Also (v1.12.18): s-library.html duplicated cl-book-filter / cl-search / cl-grid
from s-characters.html, breaking getElementById. Characters panel in Library
now redirects to s-characters instead of duplicating its DOM nodes.
Also (v1.12.19): engines.js triggers a second loadVoiceLibrary() after nav.js
already rendered voices, blanking the list briefly. Second call now silently
re-fetches without clearing the list when voices are already present.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
loadSettings() was crashing with "Cannot set properties of null" because
many settings input elements were moved out of their original sections
during the Library restructure. All bare .value assignments are now
routed through a local sv() helper that silently skips absent elements.
This was also blocking My Voices from rendering on page load.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After a batch benchmark completes, successful entries are saved to each
voice's meta.json via POST /api/voice/meta. window.loadVoiceLibrary() is
then called so My Voices Factor + WPM columns update instantly without
a manual reload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Voice selection list now shows flag + gender symbol on each row.
Batch results table adds Lang and Gender columns. All columns are
sortable by clicking the header (↑↓ indicator); defaults to Factor
descending. Sort logic handles strings (locale) and numbers uniformly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Voices table: removed Length, Duration, Time — only Factor and WPM remain
from benchmark data. Setup → Benchmark batch results now shows Duration,
Factor (sorted fastest-first, colour-coded), Time, and WPM. Factor replaces
Avg RTF with the same data flipped to a more intuitive direction (higher=better).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The old combined `21.8s · 1.30x` Speed cell is replaced by two separate
sortable columns:
- Factor (x.xx): audio÷render multiplier, green/amber/red colour-coded
- Time (Xs): total render time
Column order: Length · Duration · Factor · Time · WPM · Seed · dBFS · ...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the bench/wpm cells only updated after loadVoiceLibrary() at
the end of the full batch. Now each row is patched in-place immediately
after its voice finishes, for both batch and single-voice benchmarks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
WPM (words per minute) is derived from the benchmark audio duration and
sentence word count, revealing how fast a voice speaks — independent of
GPU speed. 130–180 wpm is comfortable for audiobooks. Column is sortable.
Info (ⓘ) icons on Speed and WPM headers explain both metrics on hover.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Speed column now displays 1.32× instead of 1.3× for better precision.
Sorting by Speed ranks by the RTF multiplier (faster = higher ×) rather
than raw elapsed time, so the sentence length no longer skews the ranking.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Voices cloned from a Script Rehearsal now show the script title in the
SOURCE column without requiring manual entry. `_displaySource()` parses
the note field (`Rehearser · <title> · <character> — …`) and returns the
title segment as the source. Explicit `origin` values still take priority.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Source input to voice inspector panel (saves to origin in meta.json)
- Auto-detect fish-audio voices via tag — SOURCE column now shows
"fish-audio" for tagged voices even without an explicit origin field
- Bulk "Set source" action in the multi-select toolbar
- Source cell in table reflects the live-displayed value and updates
immediately when changed via the inspector
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Casting completion auto-saves to Rehearser IndexedDB (same format
as script rehearsals). Manual speaker corrections in the cast view
debounce-save after 1.5s.
- rehId tracked across session (stored in localStorage draft) so
updates go to the same record instead of creating duplicates.
- Voice assignments made in Script Rehearser survive an auto-update:
only script text and emotions are overwritten; voice/instruct/soul
are merged from the existing record.
- "Edit in Rehearser" button in the completed cast footer opens the
saved record directly in Script Rehearser, ready for voice casting.
- Expose rehDbGetById + rehLoadRecord globally from rehearser.js.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Saves accumulated segments after every chunk. On page refresh or crash,
reopening Cast as Audiobook for the same document restores the session
automatically — shows a banner with completion % and save age.
Manual speaker reassignments in the cast view are also autosaved so
review corrections survive a refresh. Draft clears when the script is
saved to Script Rehearsals or a fresh Recast All is triggered.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Click any gap divider to reveal the hidden segments between two Unknown
passages inline — shows speaker + text so context is clear before
making an assignment. Displays line count ("42 lines hidden — click
to expand") so users know what they are opening.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Pulsing blue dot on the Read Aloud nav item while casting is active
- Navigating away and returning restores the cast panel automatically
- Reassigning a segment's speaker decrements the old count so the
Characters Found panel stays in sync; speakers at 0 disappear
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Conversation: adjustable noise gate slider (RMS threshold + 300 ms
minimum burst duration) prevents short noise spikes from triggering
STT; level meter shows gate position as a blue marker
- Conversation stats panel now collapsible (chevron button) to free
chat width; floating expand button restores it; state persists
- Read Aloud: text search input in PDF toolbar (Enter = next hit,
Shift+Enter = previous, Esc = clear)
- Sidebar: tooltip now works for all item types including sub-items
that had no .nav-label span (text extracted by stripping icon/badge)
- Sidebar active section indicator added (.nav-tree-item.active was
previously unstyled — active section now has bg + right accent bar)
- Casting audiobook: prompt instructs LLM to handle ?« / !« endings
and unclosed » at passage end as dialogue; deterministic fallback
also handles unclosed opening quote
Bumps to v1.12.2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sidebar collapses to a 56px icon rail; hovering flies the full menu out as
an overlay (icons + titles/nested items). Language picker moved to Settings.
- Unify settings collapsibles to the app's standard card-collapse style:
Conversation, Read Aloud (drag-&-drop now inside), Try It Out, Casting panel.
- Read Aloud: reordered (settings → toolbar → document → transport/synth) and
the document fits the viewport height so controls below stay visible; remove
the redundant My Books card (lives in Library → Books).
- Conversation: stacked full-width config, fills viewport height; fix
intermittent webm decode in hands-free mode (recorder restarts cleanly,
in-browser WAV encode); barge-in via Live agent.
- Fix casting feed overflow that pushed the sidebar off-screen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Conversation Playground: STT/LLM/TTS/System prompt now full-width stacked.
- Fix conversation mic upload on ARM64: decode recording in-browser to
16 kHz mono WAV, bypassing server ffmpeg webm/EBML parser (with fallback).
- Document v1.10.0 (unified Library + character tags) and v1.10.1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restructure the sidebar into Voices / Speak / Setup groups and fuse the
Read Aloud and Script Rehearser features into one workspace:
- New combined Library section (Books · Theater Plays · Characters/Cast)
with cross-links (Rehearse a book, Read Aloud a play).
- Character tags like voice tags: one record, many productions, reusable
across books/scripts; seeded with origin book, editable in the editor.
- Shared cast resolution: opening a production fills empty cast slots from
the character roster (matched by book OR tag); voice choices written back
on save. Productions joined by normalized title (prodKey).
- Tags nav group for voices (distinct tags + counts, Cloned/Designed/Fav).
- Fold standalone Characters into Library; Read Aloud now a single entry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Longer mixed DE/EN phrase covering dotted numbers, compound nouns,
umlauts, special characters, English technical vocabulary, time
formats and prose — reveals more of a voice's character per seed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- voice-inspector: move `curGender` const before its first use — it was
declared at line 86 but referenced at line 61, causing a ReferenceError
(temporal dead zone) that silently aborted selectVoice() on every click
- Calc dB, Precompute, Batch Seeds now filter to checked voices when a
bulk selection is active, matching Benchmark's existing behaviour
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Auto-scroll to bottom is now gated on the user being within 80px of
the bottom. Scrolling up to review or edit a line freezes the feed
in place — new rows are still added, but the viewport doesn't move.
- Row-trimming is likewise suppressed while the user is scrolled up,
so old lines stay visible as long as they're being read/edited.
Trim limit raised from 80 → 600 rows so almost nothing is evicted.
- A floating "↓ Live" pill button appears at the bottom of the feed
whenever the user has scrolled up. Clicking it returns to the live
bottom and re-enables auto-scroll.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Casting view improvements:
- "LLM Reading…" row now shows a preview of the passage being processed.
A chevron button expands it to a full scrollable view of the passage
text (up to 260px), so you can follow what the LLM is reading live.
- Page-break dividers ("Page N") appear in the casting feed whenever the
source PDF page changes, giving a real-time view of page boundaries.
Bug fix:
- Page numbers in the Rehearser script were 0-indexed (PDF-internal) so
the first page break showed "Page 1" for what was PDF page 2, etc.
Now always emits 1-indexed page numbers (\f${page+1}).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When a PDF audiobook is cast and saved to the Rehearser, page breaks now
carry the source PDF page number. In the Stage view each divider renders
as "— Page N —" instead of the generic "— Page break —".
Implementation:
- audiobook.js: `audiobookBuildScript()` encodes the page number in the
form-feed line (\fN instead of bare \f) at each page boundary.
- rehearser-parse.js: `parseScript()` now matches `line.startsWith('\f')`
and extracts the trailing page number into `line.page`.
- rehearser.js: `buildScriptPage()` renders "— Page N —" when `line.page`
is set; `toScript()` round-trips the number back (\fN) so it survives
save/reload; `importPDFScript()` also encodes page numbers (\f<pageIdx+1>)
when importing screenplay PDFs directly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Exposes the `speed` parameter (0.5–2.0) on the TTS `/v1/audio/speech`
request so audio is generated at the target tempo natively via the
faster-qwen3-tts backend rather than post-processing.
- Backend: `/api/tts-preview` now extracts and forwards a `speed`
override (clamped 0.1–4.0) through the same extra-params mechanism
already used for seed/temperature; backends that reject it fall back
cleanly via `_post_tts_with_fallback`.
- Try it out: "Native Speed" number input (0.5–2, step 0.05) added to
the text card; value persists in localStorage per browser; passed as
`extra` through `createTtsAudioSource` and `generateChunkedTts`.
- Read aloud: "Native Speed" control added to the generation controls
row alongside seed/temperature; included in `readerGenParams()` and
saved/restored with library documents (each book tracks its own speed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
audiobookScopeText now records PDF page boundaries as character offsets in the
scope text; audiobookBuildScript realigns each segment against the source and
emits \f page-break markers at the nearest boundary. Saved/opened cast scripts
keep the book's pagination instead of collapsing to one flow.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Character sheets: stop sending the dead localhost:11434 default; fall back to
the server's configured llm_url so extraction uses the same working LLM.
- Audiobook casting: hoist highlightText to module scope so the "Review & cast"
manual-correction preview renders its segment rows again.
- Stage: unify narrator/dialog font size, add A-/A+ play text-size control
(scales A4 + paginated views), and make the cast chip list collapsible.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a persistent, book-scoped Character Library (new Characters section,
IndexedDB) that auto-fills from Character-sheet analysis with editable cards.
Enrich extraction with six narrative fields (backstory, relationships,
motivation, fears, mannerisms, voice/speech) plus the greyscale Good↔Evil
alignment bar, arc arrow, and 5-area Deep Analysis. Add ⋯ separators between
non-contiguous passages in Recast unknown, and harden dialogue attribution
against hallucination with same-language emotion tags.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Added a new 'Table View' button to the library sort bar.
- Implemented a CSS grid layout to display voice properties in a high-density table.
- Added sortable column headers (Name, Lang, Gender, Speed, dBFS, Length, Rating, Source, Seed, Note, Tags, Active).
- Aligned CSS grid to account for the bulk edit checkbox injection.
- Added drag-and-drop profile image support to the Inspector's large avatar.
- Ensured picture updates instantly synchronize across the List, Table, and Inspector views.
Read Aloud (new "Vorlesen" tab):
- PDF (real page render + overlay highlight) / TXT reader with live word
highlighting, voice + speed, per-sentence synthesis-state colours, zoom
(fit-width/height, two-page, ±), resume, and a server-side book library
(syncs across devices; per-unit MP3 audio fetched on demand).
Book -> multi-speaker audiobook:
- "Cast as audiobook" attributes dialogue to characters via the LLM
(guillemet/quote-style aware, turn-taking, recent-context), with a
deterministic speech-tag fallback. Editable preview, non-blocking live
casting panel, then auto-saved as a reopenable Script Rehearser play.
- Audiobook export: synthesise every cast line -> one MP3 per chapter.
Character sheets:
- LLM-extracted, self-filling RPG-style sheets (with page+quote sources)
in both Read Aloud and the Rehearser.
Also: MP3 storage + per-page/sentence export, voice-library "Precompute
embeddings" pre-warm, German "Vorlesen" i18n + flag language toggle,
large-PDF performance (lazy raster, buffer/canvas eviction, yielded parse),
and the Seed Finder changelog entry.
New: routes/reader.py, POST /api/attribute-dialogue, POST /api/character-sheets,
static/js/{reader,audiobook,character-sheets}.js, static/sections/s-reader.html.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a collapsible "🎲 Seed Finder" panel to the voice inspector pane.
Select a voice, expand the panel, set a seed range (default 1–15), and
click Run to generate audio samples with different seeds.
Each result row has Play and "Use this seed" buttons. "Use this seed"
saves the chosen seed to the faster-qwen3-tts voices.json via the new
/api/tts-voice-seed proxy endpoint (which calls /voice-seed on the TTS
server). The saved row gets a ✓ marker.
Test sentence auto-selects DE/EN mixed or EN-only based on voice name.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
splitTextIntoChunks only split on sentence terminators (.!?), so structured
text separated by newlines was never chunked — the full page went as one
request, causing a TCP timeout that the browser reported as "Failed to fetch".
Fix: process each line individually before applying the sentence regex so
newline-separated segments become their own split points. Also move
generation.js from deferred batch E into the main feature batch C so
generateChunkedTts is always defined before the user can click Generate.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Storage: IndexedDB ('reh-library' DB, 'rehearsals' store).
Blobs (recorded mic audio) stored natively; no base64 overhead.
Library panel (Phase 1):
- List of saved rehearsals sorted by last update
- Each row: character-colour avatars, title, progress bar,
line count, recorded-clip count, date
- Active record highlighted in accent colour
- Open / Export .reh / Delete per-row buttons
- Import .reh button at the top
Transport bar (Phase 3):
- Save button (updates existing record if savedId set, else creates new)
- Export button (downloads current state as .reh JSON file)
Phase 4 (session complete):
- Save to library and Export .reh buttons
Data format (.reh file):
- JSON with version=1, title, script text, cast map, backend, lineIndex
- clips serialized with audio as base64 strings (mime + b64 fields)
- clipsFromJson restores Blob objects on import
Behaviour:
- Parsing a brand-new script resets savedId → safe to save as new record
- loadRecord() restores script, cast, lineIndex, clips and jumps to Phase 2
- renderLibraryList() called on init, after save, after delete, after import
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- A4 paper page (white, serif font, shadow) renders the full script at once
so you can read ahead while rehearsing
- Each dialog block shows: character avatar (voice library photo or initial
letter), character name in their colour, TTS/Me badge, and dialog text
- Active line gets a blue left-border highlight and auto-scrolls into view
- Transport bar (sticky, above the script):
- Cast strip: all character avatars at a glance
- ⏮ Prev / ▶ Play all / ⏹ Stop / ⏭ Next / 🔁 Repeat
- Progress bar + line counter
- Auto-play: TTS lines synthesize, play, auto-advance; 'me' lines pause
and slide up a sticky recording overlay at the bottom of the page
- Recording overlay shows the line to speak, oscilloscope + meter,
Record / Stop / Keep & continue / Re-record / Skip
- voice-library.js now exports window._voices so the rehearser can resolve
voice IDs to has_picture flags and fetch /api/voice/picture/{id}
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## Script Rehearser (new feature)
- New section s-rehearser.html + rehearser.js + nav/loader wiring
- Phase 1: paste/upload script (.txt), auto-detect characters from
'CHARACTER: dialog' or ALL-CAPS screenplay format
- Phase 2: assign a TTS voice per character, or mark 'I play this'
- Phase 3: step-through rehearsal — synthesizes other characters via TTS,
shows level-meter + oscilloscope for your own lines, records them from mic
- Phase 4: session summary with per-line audio playback + download
## Connect Apps
- Removed duplicate standalone MCP/speak/hotkey full-width cards
- Kept the integration-grid cards (they use the real server URL from JS)
- Added Global Hotkey Daemon as a proper integration card with snippet-hotkey
populated by integrations.js (uses proxyBase URL dynamically)
## About page
- GET /api/changelog endpoint reads CHANGELOG.md and returns it as text
- Collapsible 'Changelog' <details> card fetches and displays it lazily
## Try It Out
- Reorganised into three cards: Voice & backend / Text to synthesize / Generate
- Backend help panel moved below the voice row (not in the same flex row)
- Style instruction field gains a dynamic badge ('style-aware ✓' / 'weak style')
and a yellow warning when a non-style-aware backend is selected while the
field is filled — wired to both backend-select change and input events
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Loader:
- Post-init modules (engines, ai-backends, generation, conversation) now
load AFTER the skeleton is removed instead of before. The UI is visible
~500 ms sooner on average; those four modules load while the user is
already browsing Voices / Clone / Design.
- Removed the sttReady event approach that was triggering a duplicate
/api/stt-backends call; init.js already populates all STT selects once
on startup.
Skeleton:
- Replaced the card-grid placeholder with a two-column workbench skeleton
(voice list rows on the left + inspector placeholder on the right) that
matches the real My Voices layout.
Connect Apps:
- MCP Server, /speak REST endpoint, and Global hotkey daemon sections
moved from Settings → About to Connect Apps, where they belong.
- About page now has GitHub + Releases links instead.
Clone section:
- Added a hint note beneath the sample-text textarea reminding the user
to replace the placeholder name (Sam / Alex / Marco …) with their own.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: VERSION was not volume-mounted, so the server always reported
1.2.0, causing browsers to serve 1-year-immutable cached JS even after
code changes.
Fixes:
- docker-compose.yml: add ./VERSION:/app/VERSION:ro volume mount.
After `docker compose up -d`, the server reads the current VERSION file
and JS is cache-busted by the correct version string.
- loader.js: append session timestamp to _appVersion so every page load
generates a unique JS URL. JS is always fresh regardless of whether
VERSION is current, at the cost of one network round-trip per file per
session (acceptable for a local tool).
- s-clone.html: embed EN default text directly in the textarea so the
field is never empty even before JS runs.
- voice-clone.js: remove the 'skip if already filled' guard in
initCloneSampleText so navigating back always resets to the language
text; call refreshSttBackends on load with sttReady event fallback.
- stt.js: dispatch 'sttReady' event after all STT listeners are wired.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. Sample text: expose initCloneSampleText as a window function and call
it from nav.js runSideEffects when the clone section is activated,
ensuring the textarea is always populated even if the IIFE ran before
the element existed.
2. Better sample texts: all 8 languages rewritten to ~38 words / ~15 s,
first-person, phonetically rich, proper Unicode diacritics.
3. Live mic monitor: a level-meter (18-bar) + scrolling oscilloscope
canvas (ring-buffer, 300 px, colour-coded) added to the microphone
card. "Check level" / "Stop monitor" buttons start/stop it
independently; clicking Record starts it automatically.
Uses raw mic constraints (no echo-cancel / AGC) for cleaner voice clone
audio. Mic gain slider and dB readout included.
4. Recording quality: MediaRecorder now requests audioBitsPerSecond:256000
in both voice-clone.js and stt.js.
5. STT engine picker: Recognition engine <select> + Refresh button added
above the Auto-transcribe button in Step 3. refreshSttBackends() now
syncs both stt-tts-stt-backend and clone-stt-backend. The transcribe
call passes the chosen backend to /api/transcribe.
6. File input: explicit extension list added to accept= for OGG/OPUS.
7. CSS: .mic-live-wave style added (dark/light theme variants).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the 'Read aloud' sample-sentence box back to the microphone card
in the Clone a Voice tab. Language switcher covers EN/DE/IT/ES/FR/PT/NL/PL
with phonetically diverse sentences (same as the My Voices panel).
Uses the existing .sample-read-box / .sample-sentence CSS so it looks
identical to the equivalent panel in the voice library.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sentence text in typing bubble:
- Backend sends 'text' field with each audio SSE event (the sentence
being synthesised)
- Frontend audio queue stores {url, text} pairs
- playNextAudio() writes the sentence text into the '...' typing bubble
when LLM tokens haven't arrived yet (convCurrentSentenceBubble)
- convCurrentSentenceBubble cleared as soon as first LLM token arrives
so normal streaming takes over seamlessly
VAD noise fixes:
- VAD_THRESHOLD: 0.01 → 0.02 (background noise no longer counts as speech)
- VAD_MIN_REC_MS: 400 → 800ms (8/10s wait before silence detection starts,
gives user time to begin speaking without initial noise triggering send)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Speech gate (silence detection):
- vadHadSpeech flag: VAD auto-stop cancels without calling STT when no
speech was detected (fixes "[STT] No speech detected → gibberish" loop)
- cancelNextBlob flag: onstop skips processBlob when VAD cancels silently
- vadLastVoiceMs: gates preview transcription on actual detected speech
(prevents "reich" hallucination on initial silence chunks)
Hallucination filter:
- Client: HALLUCINATION_RE strips known Whisper phantoms from preview
- Server: _is_hallucination() in generate() treats "reich" / "danke" /
"thank you" etc. as "No speech detected" → never reaches LLM
Latency:
- VAD_SILENCE_MS: 1500 → 1000 ms (sends 500 ms sooner per turn)
- VAD_MIN_REC_MS: 500 → 400 ms
- MediaRecorder timeslice: 2500 → 1500 ms (preview text updates faster)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace serial LLM-wait-TTS with overlapped execution:
- LLM streams via background thread → asyncio.Queue (non-blocking event loop)
- _sentence_split() detects sentence boundaries in the token stream
- asyncio.create_task fires TTS for each sentence immediately — TTS for
sentence 1 runs while LLM is still generating sentences 2, 3, …
- Audio chunks stream to frontend in order as each task completes
- Time-to-first-audio drops from (LLM total + TTS total) to
roughly (LLM time-to-first-sentence + TTS latency for one sentence)
Frontend audio queue:
- enqueueAudio() / playNextAudio() chain multi-chunk responses seamlessly
- clearAudio() stops playback and cancels queue on new turn or mic click
- scheduleAutoMic() waits for queue to drain before restarting mic
- Error paths clear the queue to avoid stale audio playing after failure
Also fix missing contextlib import (silent bug when audio temp files
needed cleanup in the STT path).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Conversation playground:
- Live speech preview: MediaRecorder sends accumulated audio to
/api/transcribe-bytes every 2.5 s; interim Whisper result shown in
the text input field while recording. Web Speech API tried first as
a faster path when available (HTTPS/localhost).
- VAD auto-stop: AudioContext AnalyserNode measures RMS every frame;
auto-stops after 1.5 s silence with a visible countdown. Auto-stop
toggle to revert to click-to-stop.
- Hands-free mode: mic auto-restarts after the agent finishes speaking
via audio.ended event + generation-counter cancellation. Hands-free
toggle (on by default) to disable.
Navigation:
- Persist active section and sub-page in localStorage; hard-reload
returns to the same page instead of always jumping to My Voices.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously: container names and dynamic card URL overrides only
written to localStorage — lost when accessing from a different
browser or after clearing browser data.
Now:
- engine_container_names added as persisted settings key (dict)
- All container name inputs tagged data-cn-key for loadSettings() lookup
- loadSettings() restores container names + dynamic URLs from server
- settings.js exposes _saveEngineContainerNames() and
_saveEngineLocalUrls() globally so engines.js / ai-backends.js
can call them on every input event
- Static cards (ai-backends.js initStaticDockerManagement): reads
saved name from engine_container_names first, localStorage fallback
- Dynamic Docker stack cards (engines.js): same priority for both
URL and container name; both write to server on change
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move mic-blocked warning into conv-chat-window (prepend) so it scrolls
with the chat instead of pushing the input bar off-screen
- Remove min-height:400px from conv-chat-window; give conv-chat-panel a
viewport-relative height so the input bar is always visible at the bottom
- loader.js: append ?v=<timestamp> to section fetches to bust browser cache
(was serving stale s-conversation.html after updates)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add VERSION file (1.1.0) at repo root
- core/constants.py: expose __version__ read from VERSION file
- routes/admin.py: GET /api/version endpoint returns {version}
- Settings → About: display "v1.1.0" next to app name via /api/version fetch
- CHANGELOG.md: full rewrite following Keep a Changelog + Semantic Versioning
- [Unreleased] staging section at top
- [1.1.0] 2026-05-29 — security, perf, refactor, UX changes from this session
- [1.0.0] 2026-05-28 — all pre-session features documented
- Compare links at bottom pointing to GitHub
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Frontend:
- Add pill-shaped text input + send button (→) to the left of the mic button
- Enter key or → click sends text directly without recording audio
- Input is disabled while a turn is processing; cleared on submit
- Welcome message updated to mention both input methods
- New CSS: .conv-input-bar, .conv-text-row, .conv-text-inp, .conv-send-btn,
.conv-divider (visual separator between text and mic sections)
Backend:
- /api/conversation/turn: audio is now optional (UploadFile | None)
- New text form field — when provided, STT step is skipped and text is
used as the transcript directly; SSE emits transcript event with stt_ms=null
- Raises 400 if neither audio nor text is supplied
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Engine cards (all TTS + STT, dynamic Docker stack + static):
- Container name field now always visible on every card (was hidden for
absent/not-installed containers)
- Connect button moved out of URL row into a dedicated dc-controls-row;
restyled as solid blue primary CTA
- Connect now toggles to Disconnect (green) when reachable, persists state
- On successful connect, URL is auto-applied to Settings so the backend
appears in TTS/STT dropdowns immediately — no separate "Use as" click needed
- "Use as TTS/STT" button updated: larger, teal "apply" style, tooltip,
gains .active highlight once applied, right-aligned as the trailing action
- Unified control order on every card: [Connect/Disconnect] [Stop|Start|Restart] [Use as →]
- initStaticDockerManagement rebuilt to use the same dc-controls-row layout
as dynamic cards, moving .llm-local-ping from URL row at init time
Conversation section:
- Fix chrome://flags URL being white-on-grey inside the mic-blocked warning
box (global code{background:var(--panel)} overrode the inherited white text)
- Inline-styled the <code> element with a dark translucent background
- Added a Copy button so users can copy the URL without selecting invisible text
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>