String sort put "14.85" before "2.47". Now uses a custom comparator
that strips % and non-numeric markers before comparing as float.
Non-numeric values ("—", "server") sort to the bottom.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remote engines run models server-side so local RSS never changes.
Show "server" instead of "—" to make the reason clear.
Local engines show measured MB; local already-loaded shows "—".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
300s was too long; nobody waits 5 min for a result.
30s gives slow remote servers a fair window while still failing fast.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add _api_base() to strip endpoint-specific path suffixes before probing
/models, /metadata, /info — fixes /transcribe/models 404 spam when URL
ends with a custom path like /v1/transcribe
- Increase default transcribe() timeout 60s → 300s — WhisperX with speaker
diarization (pyannote) takes 2-5 min and was always timing out
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add "WhisperX server" quickstart template pre-filled with /transcribe endpoint
- Add tooltip to STT URL field explaining that non-standard servers (WhisperX)
should use the full endpoint as the URL, not /v1
- _url_field_lb accepts optional tooltip= kwarg
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- benchmark.py: measure RSS delta via /proc/self/status before/after each
transcription; add ram_mb field to BenchRow
- gtksettings.py: add RAM (MB) column to results table (index 8); tooltip
column shifted to index 10
- CHANGELOG.md: full history from v2.02.00 through v2.03.01
- README.md: benchmark description updated to mention RAM column
- MANUAL.md: benchmark result columns as table; Log tab documents
level filter dropdown
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Stability: remove blocking TCP socket call from _collect() — was
freezing the GTK main thread on Save when wakeword server unreachable;
now checks async in background and logs result
- Thread safety: fix _ww_load() reading GTK widget from background thread;
capture URI on main thread before spawning
- WhisperX 404: _transcribe_remote now detects non-standard paths
(anything other than /v1) and uses the URL as the full endpoint,
so http://host/transcribe works without /audio/transcriptions appended
- Log levels: logbuffer stores (ts, level, msg) tuples; log() accepts
level= (DEBUG/INFO/WARNING/ERROR); Log tab gets a Level dropdown
(Verbose/Info/Warning/Error) that filters displayed entries live
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a "Server preset" combo in the Hands-free wakeword card that lists
all configured wakeword engines by name. Selecting one auto-fills the
URI and model fields and re-probes the connection. Selection persists
via wakeword_active in config.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- About tab: render License with _md_panel instead of _text_panel
- Benchmark pane: set_size_request 320px min, shrink=False on both sides
so the engine list and results table are always visible
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After running a benchmark, each engine's best result (time, accuracy)
is persisted to config and shown as a small info line in the Engines tab
when that engine is selected. Updates live as the benchmark runs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- stt.py: add ModelMeta dataclass, fmt_languages(), list_models_meta(),
detect_remote_device(); refactor list_models() to delegate
- benchmark.py: add languages field to BenchRow; fetch via _get_langs()
with URL-level caching using list_models_meta()
- gtksettings.py: show language labels per engine in checkbox list;
add language codes to search filter; add Lang column to results table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Tab font-size 12px → 14px (matches body text)
- Inactive tabs: muted foreground color so they're clearly readable
but visually distinct from the active tab
- Active tab: bold + blue (#1a73e8), slightly more padding (8px 18px)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Schema: MAJOR.FEATURE.FIX
MAJOR — breaking changes / major redesign
FEATURE — two-digit, new user-visible features (00-99)
FIX — two-digit, bug fixes within a feature release (00-99)
Renamed 2.1.2 → 2.01.02 to start the new format.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_stt_commit() accesses self.stt_name which only exists after the Engines
tab is lazily built. Guard with hasattr so running a benchmark directly
from the Benchmark tab no longer throws AttributeError.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Engine checklist and results table now split by a Gtk.Paned (vertical)
so the user can drag the divider to give more room to either panel
- WAV and reference .txt paths are written to disk (save()) the moment a
file is picked via the file chooser, without needing to click Save;
also saved on Run if they changed since the last disk write
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Benchmark tab: scrollable engine checklist above Run with live
reachability dots (green/red), name+model+URL filter, All/None buttons;
_run_bench respects selection and shows clear message when nothing ticked
- About tab: changelog now rendered via _md_panel (markdown headers, bold,
lists) instead of plain monospace _text_panel
- Manual tab: graceful fallback with clickable GitHub link when MANUAL.md
is not installed; build-deb.sh now copies MANUAL.md from repo root so
/opt/blitztext/MANUAL.md exists in future installs
- STT quickstart templates expanded: Speaches docker, whisper.cpp server,
NVIDIA NIM/Parakeet, five built-in local model sizes (tiny→large-v3)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap ListStore in TreeModelSort and set sort_column_id on every column
so clicking any header sorts ascending/descending.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Engines tab: move Test button out of the toolbar, place it in a row
directly beside the result label so button and output are co-located;
result label is now selectable so error text can be copied
- Benchmark tab: add bench_wav/bench_ref/bench_expand_models to Config;
file pickers restore last-used paths on open; any change (file-set,
toggle, or Run) writes directly to cfg so paths survive without Save;
[benchmark] section written to config.toml on Save
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- stt.detect_remote_device(): probes /info (faster-whisper-server) then
/metadata (NVIDIA NIM) to detect CUDA vs CPU; cached per unique URL
- BenchRow gains url field; Device column now shows "CUDA" for GPU remotes
instead of the generic "remote"
- Benchmark table gains URL column (scheme stripped, max 180px wide)
- "Test all models per engine" checkbox: fetches list_models() for each
remote engine and expands to one row per model when checked
- benchmark.run() gains expand_models parameter
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _run_bench: deduplicate STT engines by name before running; show a
warning in the summary line listing which names were skipped
- _bench_add_row: replace raw HTTP error strings with human-readable
reasons ("Wrong model name", "Server offline", "Timed out", etc.);
full raw error stored in hidden column 7 shown as row tooltip on hover
- bench_store: added 8th column (tooltip text); tree.set_tooltip_column(7)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
install -m644 guarantees world-readable regardless of umask, so
`sudo apt install ~/blitztext_*.deb` works without a manual chmod.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- BenchRow gains `best_for` field: "Short clips" / "Short / medium" /
"Long / batch" / "Streaming" — derived from engine type and model name
- Device now shows "CUDA" instead of "GPU" for clarity
- Benchmark table gains a "Best for" column between Device and Time(s)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- stt._transcribe_remote: no longer falls back to "whisper-1" when
engine.model is empty — omits the field entirely so Riva/NIM uses its
default model instead of rejecting the request with HTTP 400
- gtksettings._page: switch vertical scroll policy to ALWAYS and disable
overlay scrolling so the scrollbar is permanently visible, making
it obvious when a tab has more content below the visible area
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- stt.list_models(): fall back to /metadata (NVIDIA Riva/NIM) when /models
returns nothing — extracts shortName and strips the version tag suffix
- Settings: show "type model name manually" placeholder when fetch returns
empty and no model is currently set
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
numpy is a runtime dep bundled in the .deb venv but not in the dev
.venv, so Pylance can't resolve it at edit time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause of session freeze confirmed: a 15 000-char code block was typed
character-by-character via xdotool at 12ms/char = ~3 min, flooding the X11
per-client event buffer until the entire session froze.
- paste.py: any text >300 chars or containing newlines auto-upgrades to
clipboard paste (instant Ctrl+V) regardless of configured output mode.
xdotool type is kept only for short single-line text where it matters.
- daemon.py: replace on_token "".join(acc) accumulation (O(n²) for long
code blocks) with a sliding deque that shows only the last 400 chars in
the overlay — Pango no longer re-lays out a growing 15 KB string on each
incoming token.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- daemon: GLib.timeout_add (thinking animation) was called from a background
thread — NOT thread-safe in PyGObject/GTK3 and the prime suspect for the
session-level freezes. Fixed via idle_add so timeout_add runs on main thread.
- daemon: add SIGQUIT handler — kill -QUIT <pid> dumps all thread stacks to
the log so the freeze location can be identified without rebooting.
- daemon: VAD hard-timeout safety net — if LevelMeter fails to open the mic
(device busy because wakeword pw-record holds it), dictation now auto-stops
after 30s instead of hanging forever. Wakeword sessions also capped at 60s.
- overlay: coalesce set_level() calls the same way set_text() is coalesced,
so mic level spikes from 3 simultaneous pw-record streams can't flood the
GTK main loop either.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- llm.py: catch OSError/TimeoutError (socket.timeout is NOT a URLError subclass)
and bare Exception so no path kills the background thread silently
- daemon.py: catch unexpected exceptions from llm.chat and surface them as
overlay errors instead of silent thread death
- daemon.py: reject LLM responses that are >80 % whitespace (cold-start
model returning spaces/blank lines gets discarded, not typed)
- daemon.py: show transcribed text in overlay immediately before thinking
animation so user can see what was heard while LLM processes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add expand_spoken_punctuation() in quality.py: converts spoken phrases
"new line" / "neue Zeile" → \n and "new paragraph" / "neuer Absatz" → \n\n
before text is typed; xdotool/wtype handle \n as Return
- Daemon: pulse "⏳ Thinking..." animation in the overlay while waiting for
the first LLM token (400ms interval), replaced automatically when streaming
starts so the user sees activity during cold-start latency
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add llm_engine field to Workflow (config.py load/save)
- Daemon picks the named engine per-preset, falls back to active engine
- Settings Presets tab: replace free-text model field with engine dropdown
populated from configured engines; "(active engine)" = default
- Fix LLM token streaming crash: coalesce GLib.idle_add calls so only
one flush is ever queued (prevents GTK main loop flooding)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
caret: change default overlay_anchor from "caret" to "pointer" — AT-SPI's
synchronous D-Bus extents read on the GTK main loop could freeze the entire
X11 session when the focused app was slow to respond. Pointer anchor is a
reliable fallback (the mouse is almost always near where you're typing) and
avoids AT-SPI entirely by default.
caret: wrap the AT-SPI rect() read in a daemon thread with a hard 300ms
timeout — so even if overlay_anchor="caret" is configured manually, a frozen
or unresponsive target app can never stall the GTK main loop.
settings: replace the Manual tab's plain TextView with a styled markdown
renderer (_md_panel): # h1/h2/h3 headings, **bold**, *italic*, `inline code`,
> blockquotes, --- rules, bullet/numbered lists, and | tables |.
settings: infobox background changed from theme-adaptive grey (unreadable on
dark themes) to a fixed blue tint (rgba 66,133,244 @ 9%) with a matching blue
border — readable in both light and dark themes.
settings: active notebook tab now shows bold text with a #1a73e8 blue colour
and extra padding, making the current tab obviously distinct.
settings: emoji picker buttons enlarged to 20 px / 38×38 px minimum (grid)
and 18 px / 34×34 px (category bar) via bt-emoji-btn / bt-emoji-cat-btn CSS
classes — previously rendered at the default button label size (~11 px).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Emoji picker: SearchEntry at top filters all categories via unicodedata.name()
in real time; category view hides while searching, restores on clear
- Manual tab: add pkg_dir/MANUAL.md to _app_paths() search list so it works in
both venv and deb installs (MANUAL.md deployed alongside the package)
- bt-infobox CSS: replace @theme_selected_bg_color (saturated blue) with a
neutral 5% mix of fg/bg so banner text is always readable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Every _labeled and _switch_row field now gets a clickable ⓘ info button
that opens a plain-language help popover — targeted at non-technical users
- New Manual tab in Settings renders MANUAL.md directly inside the dialog
- STT and LLM toolbars each get a "Quickstart ▾" button: a menu of common
providers (OpenAI, Groq, OpenRouter, Ollama, LM Studio, vLLM, llama-swap,
faster-whisper-server, NVIDIA Riva) that pre-fills the engine form in one click
- Engine type combos now show human-readable labels ("Internal — faster-whisper",
"LAN server — runs on your machine", "GPU (CUDA)", "int8 — fast, less memory")
while storing the same internal key values (no config migration needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the 60-emoji flat list with 8 standard Unicode categories
(Smileys, People, Animals, Food, Travel, Activities, Objects, Symbols),
each with 30–100 emojis. The picker now has a category tab bar at the top
(emoji icons, highlighted active tab) and a scrollable emoji grid below —
matching the layout used in WhatsApp and other messaging apps.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a 😀 button next to the Icon (emoji) entry in Settings → Presets.
Clicking it opens a GTK popover with 60 common emojis in a scrollable
flow grid; selecting one writes it into the field and closes the picker.
The entry still accepts direct keyboard input as before.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In Python 3.12 the 'except ... as e' variable is deleted after the except
block exits, so a plain lambda that closes over 'e' raises NameError when
GLib fires it later. Bind e as a default argument at lambda creation time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Globs don't expand in redirect targets. Use the venv Python to resolve the
exact site-packages path instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On Debian/Ubuntu, python3-gi is installed into the non-versioned
/usr/lib/python3/dist-packages path which venvs exclude even with
--system-site-packages. A debian-system-packages.pth file adds it to
sys.path so `import gi` works from the venv.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
'python -m blitztext' adds cwd to sys.path, so running blitztext from inside
a directory that contains a blitztext/ folder shadows the installed package.
A proper shebang entry-point script sets sys.path[0] to the script's own
directory (venv/bin), making the launcher cwd-independent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each apt package must be a separate array element; passing them as one quoted
string caused apt-get to fail with "unable to locate package <all names>".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Check for python3-gi (PyGObject), xdotool, libnotify-bin, and pipewire-bin
before creating the venv; if any are missing, run sudo apt-get install
automatically so the install works out of the box on a fresh system.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves the venv out of the source tree to ~/.local/lib/blitztext so it works on
CIFS/SMB shares (--copies avoids the lib64 symlink that CIFS rejects). Copies
the blitztext package into site-packages so the cloned source folder can be
deleted immediately after running install.sh. Adds a ~/.local/bin/blitztext
launcher and installs hicolor icons.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All rows in the Input tab (labeled, key_field, url_field, sound_field) now share
a single LW=175 label width, eliminating the jagged left edge on input fields.
Added X-GNOME-Autostart-Delay=12 to the .desktop entry so gnome-shell, AT-SPI,
and the input stack are fully initialised before Blitztext connects — fixes the
session crash on first login after installation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>