engines: Test button inline with result + selectable; benchmark: persist WAV/ref paths (v2.0.7)

- 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>
This commit is contained in:
mARTin-B78 2026-06-09 18:29:01 +02:00
parent 1c16cfeb0f
commit 5458fdc1eb
3 changed files with 49 additions and 5 deletions

View File

@ -6,4 +6,4 @@ counterpart to the macOS Blitztext menu bar app: it runs natively on the host
(not in a container) so it can type into any application via xdotool. (not in a container) so it can type into any application via xdotool.
""" """
__version__ = "2.0.6" __version__ = "2.0.7"

View File

@ -120,6 +120,10 @@ class Config:
# Wakeword engines to compare in the benchmark (each a wyoming-openwakeword # Wakeword engines to compare in the benchmark (each a wyoming-openwakeword
# endpoint). Empty → the benchmark falls back to the live [wakeword] above. # endpoint). Empty → the benchmark falls back to the live [wakeword] above.
wakeword_engines: list[WakewordEngine] = field(default_factory=list) wakeword_engines: list[WakewordEngine] = field(default_factory=list)
# STT benchmark: last-used WAV / reference transcript paths and options
bench_wav: str = ""
bench_ref: str = ""
bench_expand_models: bool = False
# workflows # workflows
workflows: list[Workflow] = field(default_factory=list) workflows: list[Workflow] = field(default_factory=list)
@ -236,6 +240,9 @@ def load(path: Path = CONFIG_PATH) -> Config:
tts_api_key_env=tts.get("api_key_env", ""), tts_api_key_env=tts.get("api_key_env", ""),
tts_model=tts.get("model", ""), tts_model=tts.get("model", ""),
tts_voices=list(tts.get("voices", ["alloy", "echo", "fable", "onyx", "nova", "shimmer"])), tts_voices=list(tts.get("voices", ["alloy", "echo", "fable", "onyx", "nova", "shimmer"])),
bench_wav=data.get("benchmark", {}).get("wav", ""),
bench_ref=data.get("benchmark", {}).get("ref", ""),
bench_expand_models=bool(data.get("benchmark", {}).get("expand_models", False)),
) )
for entry in data.get("workflow", []): for entry in data.get("workflow", []):
@ -371,6 +378,11 @@ def save(cfg: Config, path: Path = CONFIG_PATH) -> None:
"model": cfg.tts_model, "model": cfg.tts_model,
"voices": cfg.tts_voices, "voices": cfg.tts_voices,
}, },
"benchmark": {
"wav": cfg.bench_wav,
"ref": cfg.bench_ref,
"expand_models": cfg.bench_expand_models,
},
"wakeword_engine": [ "wakeword_engine": [
{"name": e.name, "uri": e.uri, "model": e.model} for e in cfg.wakeword_engines {"name": e.name, "uri": e.uri, "model": e.model} for e in cfg.wakeword_engines
], ],

View File

@ -1146,9 +1146,8 @@ notebook.bt-nb tab:checked label {
qs.set_tooltip_text("Fill the form from a common service template") qs.set_tooltip_text("Fill the form from a common service template")
qs.connect("clicked", self._show_stt_templates) qs.connect("clicked", self._show_stt_templates)
bar.pack_start(qs, False, False, 0) bar.pack_start(qs, False, False, 0)
# Right: actions # Right: actions (Test moved to its own row below the config)
for label, cb, tip in ( for label, cb, tip in (
("Test", self._stt_test, "Record 4 s and transcribe to test this engine"),
("Delete", self._stt_delete, "Remove this engine preset"), ("Delete", self._stt_delete, "Remove this engine preset"),
("", lambda _b: self._refresh_status(), "Re-check connection status"), ("", lambda _b: self._refresh_status(), "Re-check connection status"),
): ):
@ -1185,8 +1184,19 @@ notebook.bt-nb tab:checked label {
tooltip="Precision of the model. int8 is fastest and uses least memory. " tooltip="Precision of the model. int8 is fastest and uses least memory. "
"float16 is most accurate. Auto lets Blitztext decide.") "float16 is most accurate. Auto lets Blitztext decide.")
self.stt_result = Gtk.Label(xalign=0.0); self.stt_result.set_line_wrap(True) test_row = Gtk.Box(spacing=10)
box.pack_start(self.stt_result, False, False, 2) test_row.set_margin_top(6)
stt_test_btn = Gtk.Button(label="Test")
stt_test_btn.set_tooltip_text("Record 4 s and transcribe to test this engine")
stt_test_btn.connect("clicked", self._stt_test)
stt_test_btn.set_valign(Gtk.Align.START)
test_row.pack_start(stt_test_btn, False, False, 0)
self.stt_result = Gtk.Label(xalign=0.0)
self.stt_result.set_line_wrap(True)
self.stt_result.set_selectable(True)
self.stt_result.set_valign(Gtk.Align.START)
test_row.pack_start(self.stt_result, True, True, 0)
box.pack_start(test_row, False, False, 2)
self._stt_load(self.stt_combo.get_active()) self._stt_load(self.stt_combo.get_active())
return box return box
@ -1842,17 +1852,31 @@ notebook.bt-nb tab:checked label {
ft = Gtk.FileFilter(); ft.set_name("Text (.txt)"); ft.add_pattern("*.txt"); reff.add_filter(ft) ft = Gtk.FileFilter(); ft.set_name("Text (.txt)"); ft.add_pattern("*.txt"); reff.add_filter(ft)
self.bench_ref = _labeled(page, "Reference (.txt)", reff) self.bench_ref = _labeled(page, "Reference (.txt)", reff)
# Restore saved paths
if self.cfg.bench_wav and Path(self.cfg.bench_wav).exists():
wavf.set_filename(self.cfg.bench_wav)
if self.cfg.bench_ref and Path(self.cfg.bench_ref).exists():
reff.set_filename(self.cfg.bench_ref)
def _on_wav_set(_b): def _on_wav_set(_b):
fn = wavf.get_filename() fn = wavf.get_filename()
if not fn: return if not fn: return
self.cfg.bench_wav = fn
p = Path(fn) p = Path(fn)
for ext in (".reference.txt", ".txt"): for ext in (".reference.txt", ".txt"):
cand = p.with_name(p.stem + ext) cand = p.with_name(p.stem + ext)
if cand.exists(): if cand.exists():
reff.set_filename(str(cand)) reff.set_filename(str(cand))
self.cfg.bench_ref = str(cand)
break break
wavf.connect("file-set", _on_wav_set) wavf.connect("file-set", _on_wav_set)
def _on_ref_set(_b):
fn = reff.get_filename()
if fn:
self.cfg.bench_ref = fn
reff.connect("file-set", _on_ref_set)
run_row = Gtk.Box(spacing=16) run_row = Gtk.Box(spacing=16)
run_row.set_margin_bottom(2) run_row.set_margin_bottom(2)
run = Gtk.Button(label="Run benchmark"); run.connect("clicked", self._run_bench) run = Gtk.Button(label="Run benchmark"); run.connect("clicked", self._run_bench)
@ -1862,6 +1886,9 @@ notebook.bt-nb tab:checked label {
self.bench_expand.set_tooltip_text( self.bench_expand.set_tooltip_text(
"Fetch every available model from each remote engine and run a separate " "Fetch every available model from each remote engine and run a separate "
"benchmark row per model — useful to compare models on the same server.") "benchmark row per model — useful to compare models on the same server.")
self.bench_expand.set_active(self.cfg.bench_expand_models)
self.bench_expand.connect("toggled",
lambda cb: setattr(self.cfg, "bench_expand_models", cb.get_active()))
run_row.pack_start(self.bench_expand, False, False, 0) run_row.pack_start(self.bench_expand, False, False, 0)
page.pack_start(run_row, False, False, 6) page.pack_start(run_row, False, False, 6)
@ -1945,6 +1972,11 @@ notebook.bt-nb tab:checked label {
def _run_bench(self, _b) -> None: def _run_bench(self, _b) -> None:
wav = self.bench_wav.get_filename() wav = self.bench_wav.get_filename()
refp = self.bench_ref.get_filename() refp = self.bench_ref.get_filename()
# Persist immediately so paths survive without clicking Save
if wav:
self.cfg.bench_wav = wav
if refp:
self.cfg.bench_ref = refp
if not wav or not refp: if not wav or not refp:
self._error("Pick a .wav file and a matching reference .txt file.") self._error("Pick a .wav file and a matching reference .txt file.")
return return