diff --git a/linux/blitztext/__init__.py b/linux/blitztext/__init__.py index 4e7061f..88b79d6 100644 --- a/linux/blitztext/__init__.py +++ b/linux/blitztext/__init__.py @@ -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. """ -__version__ = "2.0.8" +__version__ = "2.0.9" diff --git a/linux/blitztext/gtksettings.py b/linux/blitztext/gtksettings.py index b0882c7..e76de2a 100644 --- a/linux/blitztext/gtksettings.py +++ b/linux/blitztext/gtksettings.py @@ -68,10 +68,21 @@ _LLM_TEMPLATES: list[tuple[str, str, str, str, str]] = [ ("llama-swap", "http://localhost:28080/v1", "", "local", ""), ] _STT_TEMPLATES: list[tuple[str, str, str, str, str]] = [ - ("OpenAI Whisper", "https://api.openai.com/v1", "OPENAI_API_KEY", "openai", "whisper-1"), - ("Groq Whisper", "https://api.groq.com/openai/v1", "GROQ_API_KEY", "openai", "whisper-large-v3-turbo"), - ("faster-whisper-server", "http://localhost:8010/v1", "", "openai", ""), - ("Realtime (Riva / NIM)", "http://localhost:8006/v1", "", "riva_realtime", ""), + # ── Cloud / API services ────────────────────────────────────────────────── + ("OpenAI Whisper", "https://api.openai.com/v1", "OPENAI_API_KEY", "openai", "whisper-1"), + ("Groq Whisper (free tier)","https://api.groq.com/openai/v1", "GROQ_API_KEY", "openai", "whisper-large-v3-turbo"), + # ── Local servers ───────────────────────────────────────────────────────── + ("faster-whisper-server", "http://localhost:8010/v1", "", "openai", ""), + ("Speaches (docker)", "http://localhost:8080/v1", "", "openai", ""), + ("whisper.cpp server", "http://localhost:8081/v1", "", "openai", ""), + ("NVIDIA NIM / Parakeet", "http://localhost:8007/v1", "", "openai", ""), + ("Realtime (Riva / NIM)", "http://localhost:8006/v1", "", "riva_realtime", ""), + # ── Built-in local models (no server needed) ────────────────────────────── + ("Local tiny — fastest, ~39 MB", "", "", "local", "tiny"), + ("Local base — fast, ~74 MB", "", "", "local", "base"), + ("Local small — balanced, ~244 MB", "", "", "local", "small"), + ("Local medium — accurate, ~769 MB","", "", "local", "medium"), + ("Local large-v3 — best, ~1.5 GB", "", "", "local", "large-v3"), ] # (cat_emoji, label, [emojis]) — standard Unicode categories, WhatsApp-style @@ -1877,7 +1888,71 @@ notebook.bt-nb tab:checked label { self.cfg.bench_ref = fn reff.connect("file-set", _on_ref_set) + # ── Engine / model selector ─────────────────────────────────────────── + _section_title(page, "Engines to benchmark", margin_top=10) + + sel_hdr = Gtk.Box(spacing=6); sel_hdr.set_margin_bottom(4) + self._bench_filter = Gtk.SearchEntry() + self._bench_filter.set_placeholder_text("Filter by name, model or URL…") + sel_hdr.pack_start(self._bench_filter, True, True, 0) + all_b = Gtk.Button(label="All") + all_b.connect("clicked", lambda _: [cb.set_active(True) + for cb in self._bench_checks.values()]) + none_b = Gtk.Button(label="None") + none_b.connect("clicked", lambda _: [cb.set_active(False) + for cb in self._bench_checks.values()]) + sel_hdr.pack_start(all_b, False, False, 0) + sel_hdr.pack_start(none_b, False, False, 0) + page.pack_start(sel_hdr, False, False, 0) + + sel_sw = Gtk.ScrolledWindow() + sel_sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + sel_sw.set_min_content_height(80) + sel_sw.set_max_content_height(180) + sel_list = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=1) + sel_list.set_margin_start(4); sel_list.set_margin_end(4) + sel_list.set_margin_top(2); sel_list.set_margin_bottom(2) + sel_sw.add(sel_list) + page.pack_start(sel_sw, False, False, 0) + + self._bench_checks: dict[str, Gtk.CheckButton] = {} + self._bench_dots: dict[str, Gtk.Label] = {} + self._bench_sel_rows: list[tuple[Gtk.Box, str, str]] = [] # (row, name, search_text) + + for e in self.cfg.stt_engines: + row = Gtk.Box(spacing=6) + dot = Gtk.Label(); dot.set_markup(_dot(GREY)) + self._bench_dots[e.name] = dot + row.pack_start(dot, False, False, 0) + label = e.name + if e.model: + label += f" [{e.model}]" + cb = Gtk.CheckButton(label=label) + cb.set_active(True) + self._bench_checks[e.name] = cb + row.pack_start(cb, True, True, 0) + sel_list.pack_start(row, False, False, 2) + search_text = f"{e.name} {e.model} {e.url}".lower() + self._bench_sel_rows.append((row, e.name, search_text)) + + def _filter_bench(_e=None): + q = self._bench_filter.get_text().lower() + for row, _name, stext in self._bench_sel_rows: + row.set_visible(not q or q in stext) + self._bench_filter.connect("changed", _filter_bench) + + # Background reachability check for all engines + def _check_bench_status(): + for e in self.cfg.stt_engines: + ok = stt.status(e, timeout=2.0) + color = GREEN if ok else RED + dot = self._bench_dots.get(e.name) + if dot: + GLib.idle_add(dot.set_markup, _dot(color)) + threading.Thread(target=_check_bench_status, daemon=True).start() + run_row = Gtk.Box(spacing=16) + run_row.set_margin_top(8) run_row.set_margin_bottom(2) run = Gtk.Button(label="Run benchmark"); run.connect("clicked", self._run_bench) run.set_halign(Gtk.Align.START) @@ -1986,22 +2061,22 @@ notebook.bt-nb tab:checked label { self.bench_store.clear() self._stt_commit() - # Deduplicate by name — show warning if any were skipped + # Filter to checked engines (deduplicate by name as safety net) + checks = getattr(self, "_bench_checks", {}) seen: set[str] = set() engines: list = [] - skipped: list[str] = [] for e in self.cfg.stt_engines: if e.name in seen: - skipped.append(e.name) - else: - seen.add(e.name) + continue + seen.add(e.name) + if not checks or checks.get(e.name, None) is None or checks[e.name].get_active(): engines.append(e) - if skipped: - names = ", ".join(skipped) + + if not engines: self.bench_summary.set_markup( - f"Running… ⚠ Skipped duplicate engines: {GLib.markup_escape_text(names)}") - else: - self.bench_summary.set_markup("Running… (local models load on first use)") + f'No engines selected — tick at least one above.') + return + self.bench_summary.set_markup("Running… (local models load on first use)") expand = self.bench_expand.get_active() @@ -2178,11 +2253,19 @@ notebook.bt-nb tab:checked label { # ===== Manual =========================================================== def _build_manual(self, page: Gtk.Box) -> None: - text = _read_first(_app_paths()["manual"], - fallback="Manual not available in this install.\n\n" - "See the online documentation at " - "github.com/mARTin-B78/blitztext-app-linux") - page.pack_start(_md_panel(text, height=420), True, True, 0) + text = _read_first(_app_paths()["manual"]) + if text == "Not available in this install.": + # Show a clickable link instead of raw text + lbl = Gtk.Label(xalign=0.0) + lbl.set_line_wrap(True) + lbl.set_markup("Manual not bundled in this install.\n\n" + "Read it online: " + "" + "github.com/mARTin-B78/blitztext-app-linux") + lbl.set_margin_top(20); lbl.set_margin_start(20) + page.pack_start(lbl, False, False, 0) + else: + page.pack_start(_md_panel(text, height=420), True, True, 0) # ===== About ============================================================ def _build_about(self, page: Gtk.Box) -> None: @@ -2219,7 +2302,7 @@ notebook.bt-nb tab:checked label { page.pack_start(nb, True, True, 0) changelog_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - changelog_box.pack_start(_text_panel(changelog, height=300), True, True, 0) + changelog_box.pack_start(_md_panel(changelog, height=300), True, True, 0) nb.append_page(changelog_box, Gtk.Label(label="Changelog")) license_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) diff --git a/linux/packaging/build-deb.sh b/linux/packaging/build-deb.sh index 4791ebc..6dbb397 100755 --- a/linux/packaging/build-deb.sh +++ b/linux/packaging/build-deb.sh @@ -44,6 +44,9 @@ rm -rf "$VENV/lib/python"*/site-packages/pip "$VENV/lib/python"*/site-packages/p "$VENV"/bin/pip* "$VENV/lib/python"*/site-packages/setuptools* 2>/dev/null || true cp "$LINUX_DIR/README.md" "$LINUX_DIR/CHANGELOG.md" "$APPDIR/" +# MANUAL.md lives at repo root (one level above linux/) +REPO_MANUAL="$(dirname "$LINUX_DIR")/MANUAL.md" +[ -f "$REPO_MANUAL" ] && cp "$REPO_MANUAL" "$APPDIR/MANUAL.md" # 2) Launcher, desktop entry, icon, docs ------------------------------------ install -Dm755 /dev/stdin "$ROOT/usr/bin/$PKG" <<'EOF'