Commit Graph

155 Commits

Author SHA1 Message Date
ae12eea861 routing: default to transcribe, not the first preset
When no [routing] default is configured, default_preset fell back to
workflows[0]. If that was an LLM rewrite (e.g. "Improve text"), every
wakeword/voice command without a matching keyword was sent to the language
model — repeatedly failing with HTTP 502 when the LLM backend was down.

default_preset now prefers a transcribe-mode preset for the no-keyword
fallback, so the default action is plain transcription. Adds tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 09:53:37 +02:00
11e469b444 Release 1.3.0
Bump version to 1.3.0 and cut the [1.3.0] - 2026-06-07 changelog section
(wakeword pause toggle, independent hands-free audio cues, configurable
auto-stop silence, notification hygiene, PortAudio noise suppression, and the
new MANUAL.md). Refresh the README status line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 09:39:00 +02:00
ce0079e728 wakeword: independent cues + configurable auto-stop silence; add MANUAL.md
Reported issues from hands-free use:

- Wakeword WAVs didn't play: the new "Play audio cues" master switch
  ([sounds] enabled) also gated the hands-free Sound: detected/captured cues,
  so enabled=false silenced them. The cues live in a separate UI section, so
  this was surprising. Wakeword cues are now independent of that switch: they
  play whenever a file is set, and an empty field means silent (no system-chime
  fallback) — which is also how you turn a hands-free cue off. The master switch
  now governs only the manual (keyboard) before/after chimes.

- Clarified the four sound fields' tooltips/labels (detected/captured = hands-free
  only; before/after = manual only) and the empty-field behaviour.

- New "Silence to stop (s)" setting (Settings → Input → Hands-free, or
  [wakeword] silence_seconds, default 2.0): user-defined trailing-silence
  timeout for hands-free auto-stop (was hard-coded to 2.5 s).

- Add MANUAL.md documenting every setting in every tab; link it from the README.

Tests: cue independence + manual-gating + roundtrip (17 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:36:35 +02:00
436076620c audio cues: add master on/off switch; silence PortAudio teardown noise
Two follow-ups from hands-free testing:

- Audio cues had no off-switch: empty sound fields fall back to the freedesktop
  system chime, so wakeword/recording always made noise. Add a [sounds] enabled
  master flag (default true) exposed as "Play audio cues" in Settings → Input.
  When off, _play_cue/_play_sound are no-ops — fully silent operation.

- The VAD level meter (sounddevice/PortAudio) leaked harmless thread-teardown
  errors ("pthread_join ... failed", "PaUnixThread_Terminate ... failed") to the
  terminal on every clip end. PortAudio writes these straight to fd 2, so wrap
  the stream open/close in a fd-level stderr suppressor (_quiet_c_stderr).

Tests: cue gating respects the master switch (16 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 23:45:47 +02:00
11796fe02b README: full-width screenshots with a per-tab caption
Replace the cramped thumbnail tables in the root and linux READMEs with one
full-width image per Settings tab (Presets, Engines, Input, Wakeword, General,
Benchmark, Log, About), each followed by a caption explaining what the tab does.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 23:36:08 +02:00
080e7297dd wakeword: stop hands-free notifications leaking when away; surface paused state
Two reported bugs:

1) Notifications still appeared during hands-free use while away. A wakeword
   hit arriving while the previous clip was still transcribing went through
   toggle(), which popped a "Busy" notification — the away-from-keyboard
   notification storm. Wakeword triggers now call start_dictation(silent=True)
   directly, so a busy/not-ready state is ignored silently. Transcription and
   rewrite failures during a silent session are now logged instead of raising
   critical desktop notifications.

2) "Wakeword did not work" with the log line "muted via /tmp/wake_muted": a
   stale mute flag silently disabled detection. The tray "Pause wakeword"
   toggle (already added) clears it; the daemon now also logs a clear
   "Starting PAUSED" warning when it boots with the flag present.

Adds tests/test_daemon_notify.py covering silent-session suppression and the
busy-while-wakeword no-notify path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 23:10:48 +02:00
93de455b01 wakeword: reversible tray pause toggle; document quiet-session fixes
Bug: a stale /tmp/wake_muted flag silently disabled wakeword detection
with no in-app way to clear it (the flag was read but never written by
any code or UI).

- wakeword.py: expose MUTE_FILE + is_muted()/set_muted() helpers
- tray.py: add a "Pause wakeword" CheckMenuItem (shown when wakeword is
  enabled) that toggles the flag and reflects current state
- config.py: document the tray toggle instead of the bare /tmp file
- CHANGELOG: record the wakeword pause toggle and the existing notify
  hygiene fixes (transient/single-bubble notifications, silent
  hands-free sessions)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 22:17:50 +02:00
73ab26c90b Remove macOS remnants from CI/PR template; illustrate README
- ci.yml: drop the macOS build job (Xcode/xcodegen/build.sh); keep the
  Linux test job and move the secret-hygiene scan into it
- PR template: replace ./build.sh checklist item with the Linux test
  suite; fix "macOS preview scope" -> "Linux preview scope"
- README: add a control-panel + tray hero image and a Screenshots
  gallery (settings tabs + hands-free wakeword)
- Track Screenshots/wakeword.png

The BlitztextMac/ source tree, root build.sh, and stale docs/screenshots
were removed from the entire git history in a separate history rewrite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 22:11:40 +02:00
e4afb552c7 Update settings-engines.png screenshot
Refresh the Engines tab screenshot to the current UI (info box, +Stream button,
engine Name field, local/cloud Type, device & precision, status dots).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 15:21:54 +02:00
1171f8f8b0 Add updated UI screenshots and embed them in the Linux README
Refresh the panel/tray/settings screenshots, normalise "tray menu.png" to
tray-menu.png (drop the old "panel resorted.png"), and add a hero image plus a
Screenshots section (settings tabs) to linux/README.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 15:16:03 +02:00
36b00935c7 autostart: launch the currently-running copy (source vs installed)
_exec_command now detects whether Blitztext is running from a source checkout
(not /opt or site-packages) and, if so, writes an autostart entry that launches
that exact source with PYTHONPATH — so the "Launch on login" toggle no longer
reverts to a stale installed .deb when developing from source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 14:50:56 +02:00
8e1419e828 Add wakeword-specific audio cues (detected / captured)
Two optional WAVs in the Hands-free section: "Sound: detected" plays when the
wakeword fires (your cue to speak now) and "Sound: captured" plays when the
command is taken (silence/stop). For hands-free sessions these take precedence
over the general [sounds] cues, falling back to them and then to the built-in
system sound. The general cues are relabelled "Audio cues (manual dictation)".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 14:34:49 +02:00
87eee3f103 Silence notifications for wakeword-triggered (hands-free) sessions
A session started by the wakeword is marked silent: its per-dictation
notifications (Recording, Transcribing, routing match, Rewriting, done) are
suppressed via a new _dnotify helper. Keyboard/GUI-triggered dictation still
notifies normally, and real errors still show.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 11:09:04 +02:00
6448711f65 Stop notification storms: transient notifications, single instance, no empty-result alerts
- notify-send notifications are now transient (boolean:transient:true) with a
  2.5s expiry, so they no longer pile up in the message tray / lock screen.
- Single-instance guard (abstract unix socket) in the entry point: a second
  `tray`/`gui`/`run` launch exits instead of starting a duplicate daemon — the
  duplicate caused two wakeword listeners + two recorders.
- Empty/quiet/keyword-only results now only log, instead of firing a desktop
  notification on every (often false) wakeword trigger.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 10:42:13 +02:00
5ad4e92a73 Add UI screenshots and pyright config
Add Screenshots/ (panel + settings tabs: engines, input, general, benchmark,
about) for documentation, and pyrightconfig.json pointing the type checker at
the system dist-packages so it resolves PyGObject (gi).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 09:59:10 +02:00
2d777382f5 Settings accessibility: per-tab info boxes + tooltips; complete 1.2.0 changelog
Add a plain-language info box to the top of every Settings tab (Presets,
Engines, Input, General, Benchmark, Log, About), exposed to screen readers, and
tooltips (wired to ATK descriptions) on the preset, general, and input-mode
controls — for non-technical and blind users (barrierefrei).

Complete the unreleased 1.2.0 changelog with the engine manager, Benchmark tab,
custom audio cues, Log tab, click-to-bind hotkeys, the GTK rebuild, voice
routing, the modifier hotkey scheme, and the quality gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 09:48:35 +02:00
209b4ef259 Add custom WAV audio cues for recording start/stop
New sound.py plays a user WAV (or a built-in system sound) without blocking.
Config gains [sounds] before/after paths. The daemon plays the "before" cue when
recording starts and the "after" cue on every stop (stop+paste, stop+paste+Enter,
or auto-stop on silence — all funnel through finish_dictation). Input tab gets an
Audio cues section: file pickers with play-test and clear-to-default buttons.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 09:40:09 +02:00
f6cd376b9f Bump version to 1.2.0 and update CHANGELOG.md 2026-06-05 20:04:39 +02:00
da5593cb08 Auto-fill benchmark reference text file on audio select 2026-06-05 19:32:19 +02:00
90887afffb Add tooltips and ATK screen reader accessibility properties 2026-06-05 19:28:19 +02:00
47a5f409cf Fix shutil import and add Drag-and-Drop UI reordering 2026-06-05 19:26:27 +02:00
953bf9f287 Add audio feedback (beeps) on recording start/stop 2026-06-05 18:22:47 +02:00
7e0ef9c92f Add VAD auto-stop after 2.5s of silence during dictation 2026-06-05 18:16:55 +02:00
192c09bb70 Add Wakeword mic meter, model loading, and testing UI 2026-06-05 18:08:00 +02:00
a7f8440ce0 Fix wakeword socket loop to be fully asynchronous 2026-06-05 17:59:59 +02:00
3dad1082e4 Add Wakeword connection test to GUI before saving 2026-06-05 17:26:08 +02:00
fb05b6f19d Allow installer script to specify target branch via env var 2026-06-05 17:21:09 +02:00
5484bc6a1b Add Wayland, Tests, CI, and OpenWakeword integration
- Wayland support: text delivery using wtype or ydotool (paste.py)
- Testing: added Pytest coverage for routing, quality, and config logic
- CI/CD: added test-linux job to .github/workflows/ci.yml
- Feature: Hands-free dictation using an external wyoming-openwakeword
  server, respecting the /tmp/wake_muted toggle.
2026-06-05 17:16:05 +02:00
64ebfc4e1e Add one-line installer (curl | bash) for Ubuntu/Debian
install-linux.sh: standalone script that clones from GitHub, builds
the .deb, installs it with apt, and cleans up. Works on any
Ubuntu/Debian machine with a single curl command.

README: add the one-liner as the primary install option (Option A).
2026-06-05 16:14:35 +02:00
80038a37ea Rewrite README; fix fd leak, StopIteration crash, add pip Dependabot
- README.md: full rewrite with architecture, quickstart, config examples,
  privacy model, and contributing section (deduced from source)
- recorder.py: close leaked fd from mkstemp (SEC-2)
- gtkui.py: guard next() with default to prevent StopIteration (CQ-4)
- dependabot.yml: add pip ecosystem for linux/ deps (SEC-5)
2026-06-05 16:01:31 +02:00
b72936be31 Rewrite Linux app README 2026-06-05 15:37:44 +02:00
457835929e Add realtime streaming and Linux app docs 2026-06-05 15:06:17 +02:00
4667144b72 Benchmark: add Device column (CPU/GPU/remote); case-sensitive accuracy
- BenchRow gains a device field; Transcriber records its resolved device, so
  local engines report CPU or GPU and remote engines show "remote". New Device
  column in the results table.
- Accuracy is now case-sensitive by default (capitalisation counts) so an
  all-lowercase transcript no longer scores 100%. Punctuation is still ignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 14:12:16 +02:00
1b0b4bbc1f Add Benchmark tab (WAV + reference -> fastest & most accurate); fix STT Test
New benchmark.py: word-error-rate accuracy + a run() that times each STT engine
on a reference clip. Settings gains a Benchmark tab: pick a .wav and a matching
.txt, run all STT engines, see Time + Accuracy per engine and a summary of the
fastest and most accurate. Add presets for each model you want compared.

Fix "Local engine selected but the model isn't loaded." on Test: a cached
_transcriber_for() loads the local model on demand (the daemon only preloads it
when a local engine is active). Tested: local small 2.17s/100% vs remote :8010.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 11:11:50 +02:00
db387cab78 Add Name field to engine editors so engines can be renamed
Both STT and LLM engine editors gain a Name field (first field). Editing it
renames the engine and updates the dropdown on commit; Save persists it. Fixes
the inability to rename "New STT"/"New LLM" after Add.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 10:23:36 +02:00
f1ee4bd4c5 Move Whisper device/compute into Engines→STT; add LLM Type (local/cloud)
- Local-Whisper Device + Compute type now live under the STT engine section
  ("Local engine — device & precision"), removed from the General tab. The
  local engine's Model field shows the effective whisper size.
- LLM engines gain a Type field (local | cloud), mirroring STT, stored in config.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 09:48:26 +02:00
78350bdb99 Add Log tab + in-memory log buffer
New logbuffer.py captures app messages (log()) and library logs (faster-whisper,
huggingface_hub) into a ring buffer, mirrored to stderr. Settings gains a Log
tab: monospace view, 1s refresh, auto-scroll, Copy, Clear — so model
download/load progress is visible instead of an opaque "Loading…". transcribe
and daemon now log via the buffer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 09:42:48 +02:00
a508ecc69c Searchable model dropdown with a top search bar + URL reload button
Replace the model combo with a ModelPicker: an editable field plus a ▾ button
that opens a popover with a SearchEntry on top and a scrollable, filtered list
of models (HuggingFace-style). Add a refresh icon next to each engine URL that
loads models from {url}/models on demand. Verified: 8010 -> 428 models, typing
"turbo" filters to the turbo variants. You can still type a custom model name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 09:13:50 +02:00
09e3f58ba8 Add click-to-bind "Set" buttons for hotkey fields
Each key field (Start/Stop+paste/Stop+paste+Enter/Cancel, and the preset
Hotkey) gets a Set button that captures the next keypress and writes it in the
config format. Captures modifier-only chords (e.g. Ctrl+Win -> <ctrl>+<cmd>) by
accumulating pressed keys and finalising on first release; key events are
swallowed while binding so they don't leak into the UI. Tested: token mapping,
combo formatting, and the bind flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 09:07:11 +02:00
b42f526ca3 Searchable model dropdown fetched from {url}/models
The engine Model field is now an editable combo with a type-to-search
EntryCompletion (substring match). When you enter a remote engine URL it fetches
the model list from {url}/models (OpenAI `data[].id` or Ollama `models[].name`)
and fills the dropdown; local STT offers the whisper sizes. stt.list_models
added; tested live (8010 -> 428 models, 28080 -> 18, 19001 -> 1). You can still
type a custom model name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 09:02:14 +02:00
8d2bae0a27 Add grey placeholder hints to settings fields
set_placeholder_text on the preset and engine entries (URL, model, API key env,
keywords, hotkey, temperature, language) so empty fields show light-grey
examples of what to enter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 08:55:07 +02:00
44ca0feabd Redesign Settings: preset/engine managers, mic + level meter, autostart
New GTK settings with a dropdown+editor pattern across four tabs:
- Presets: select/add/delete prompt presets; edit name, keywords, hotkey, mode,
  per-preset model/temperature, and a clearly-bordered prompt textfield.
- Engines: STT and LLM engine managers with green/red online-offline status
  dots, add/edit/delete, active selector, and an STT record-and-benchmark Test.
- Input: input scheme + keys + quality-gate settings.
- General: microphone picker with a live sounddevice level meter, output,
  language, notifications, launch-on-login toggle, and local-Whisper settings.

Adds audio.py (mic enumeration via pactl + LevelMeter) and autostart.py
(~/.config/autostart entry). Recorder + daemon honour the selected mic.
Requirements gain sounddevice + tomli-w.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 23:13:36 +02:00
cf7ed562f5 Add transcription quality gate (silence + hallucination rejection)
New quality.py: analyze the recorded WAV (duration + RMS, stdlib only) and drop
clips that are too short or too quiet before transcribing; after transcribing,
reject the stock phrases Whisper invents on silence ("Thank you.", "Untertitel
…", "Vielen Dank." etc.) on short clips. New [quality] config section
(min_speech_seconds, silence_rms, reject_hallucinations, strip_trailing_punctuation).
Tested: silence rms 38 rejected, speech rms 4656 passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 23:02:55 +02:00
a104f2cfc4 Add modifier-key input scheme (whisper-key style)
New inputmode.py: Ctrl+Win start, Ctrl stop+paste, Alt stop+paste+Enter, Esc
cancel; toggle and push-to-talk variants. Low-level pynput Listener with arming
so stop/send/cancel only fire while recording and only after the start chord is
released — a stray Ctrl tap while idle does nothing (unit-tested).

Daemon split into start/finish/cancel_dictation (+ send_enter -> press Enter),
with start_input/stop_input dispatching between "modifiers" and "hotkeys" modes.
New [input] config section; paste.press_enter for auto-send. Default mode is
"modifiers"; voice-keyword routing still applies to what you say.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 20:26:59 +02:00
3026f1ff90 Add STT + LLM engine presets, rename entry to blitztext.py, macOS app icon
- stt.py: local + remote (OpenAI-compatible) transcription, TCP reachability
  status, benchmark. Tested live against faster-whisper-server :8010.
- llm.py: LLM engine presets (url/model/key/temperature) + chat + status.
  Tested live against local Qwen :19001 and llama-swap :28080.
- config: [stt]/[[stt_engine]] and [llm]/[[llm_engine]] presets with active
  selectors; defaults synthesized from legacy [whisper]/[rewrite].
- daemon: transcribe via active STT engine, rewrite via active LLM engine;
  load local Whisper only when the active STT engine is local.
- Rename package entry __main__.py -> blitztext.py (thin __main__ shim keeps
  `python -m blitztext` working). Remove rewrite.py (folded into llm.py).
- App icon extracted from the macOS AppIcon.icns; installed at several hicolor
  sizes by the .deb and used as the window icon. Drop placeholder SVG.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 20:21:03 +02:00
34e4736080 Add voice-keyword routing
One hotkey (Ctrl+Alt+Space) records; the spoken keyword at the start or end of
speech selects the preset, which is then stripped and the rest applied. New
routing.py does ASR-tolerant matching (normalize + fuzzy + edge-window scan +
token-drift slack); config gains a [routing] section and per-preset `keywords`;
the daemon adds a "route" mode and biases Whisper with the keywords as hotwords.
Falls back to a default preset when no keyword is recognised.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:47:12 +02:00
3cbea06e6c Rebuild GUI in GTK3 (native panel, unified with tray)
Replace the tkinter panel with a GTK3 control panel (gtkui.py) + settings
dialog (gtksettings.py): rounded CSD window, gradient background, circular icon
avatars, hotkey pills, hover states, Ubuntu font. Runs on the GTK main loop,
unified with the AppIndicator tray (no tkinter event-loop pump). Status updates
marshalled via GLib.idle_add. Removes the old tkinter gui.py.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:40:24 +02:00
937619a20b Merge deb-packaging: Debian installer (v1.1.0) 2026-06-04 09:25:32 +02:00
6ef421c934 Add Debian (.deb) installer (v1.1.0)
packaging/build-deb.sh builds an installable blitztext_<ver>_arm64.deb with a
desktop entry, app icon, and launcher. Bundles a relocatable venv (all Python
deps, no pip at install) and declares system deps (python3-gi, xdotool,
libnotify-bin, recorder). Built on /usr/bin/python3 so the tray works out of
the box. Installs via the Software app or `apt install ./…deb`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:25:32 +02:00
689d948fa1 Merge linux-port: Blitztext for Linux v1.0.1 (native dictation tool) 2026-06-04 08:38:22 +02:00