gtksettings: connection dots for the Wakeword (Wyoming) + TTS URLs; rename "Wyoming URI" to "Wakeword engine"; Release 1.9.0
Green/red/grey reachability dot next to the wakeword and TTS endpoint fields, matching the existing STT/LLM engine dots. Lightweight background TCP probe, refreshed on open, on reload (⟳), and on focus-out — never blocks dialog build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0d4ad0920d
commit
27cf81de46
@ -9,6 +9,16 @@ The version is defined in [`blitztext/__init__.py`](blitztext/__init__.py).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.9.0] - 2026-06-08
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Connection indicators** for remote endpoints. The **Wakeword engine** field
|
||||||
|
(Input tab — renamed from "Wyoming URI" to read more generally) and the **TTS
|
||||||
|
URL** field (Benchmark tab) now show a coloured dot: green when the server is
|
||||||
|
reachable, red when it's configured but unreachable, grey when blank — mirroring
|
||||||
|
the STT/LLM engine dots. It's a lightweight background TCP probe, refreshed when
|
||||||
|
the dialog opens, when you press ⟳, and when you leave the field.
|
||||||
|
|
||||||
## [1.8.1] - 2026-06-08
|
## [1.8.1] - 2026-06-08
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@ -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__ = "1.8.1"
|
__version__ = "1.9.0"
|
||||||
|
|||||||
@ -251,7 +251,8 @@ def _fill_combo(combo: ModelPicker, options, current: str) -> None:
|
|||||||
combo.set_text(current or "")
|
combo.set_text(current or "")
|
||||||
|
|
||||||
|
|
||||||
def _url_field(parent: Gtk.Box, label: str, placeholder: str, on_reload) -> Gtk.Entry:
|
def _url_field(parent: Gtk.Box, label: str, placeholder: str, on_reload,
|
||||||
|
dot: Gtk.Label | None = None) -> Gtk.Entry:
|
||||||
row = Gtk.Box(spacing=10); row.set_margin_top(3); row.set_margin_bottom(3)
|
row = Gtk.Box(spacing=10); row.set_margin_top(3); row.set_margin_bottom(3)
|
||||||
lbl = Gtk.Label(label=label, xalign=0.0); lbl.set_size_request(130, -1)
|
lbl = Gtk.Label(label=label, xalign=0.0); lbl.set_size_request(130, -1)
|
||||||
row.pack_start(lbl, False, False, 0)
|
row.pack_start(lbl, False, False, 0)
|
||||||
@ -271,6 +272,8 @@ def _url_field(parent: Gtk.Box, label: str, placeholder: str, on_reload) -> Gtk.
|
|||||||
btn.get_accessible().set_name("Refresh models")
|
btn.get_accessible().set_name("Refresh models")
|
||||||
btn.connect("clicked", lambda _b: on_reload())
|
btn.connect("clicked", lambda _b: on_reload())
|
||||||
row.pack_start(btn, False, False, 0)
|
row.pack_start(btn, False, False, 0)
|
||||||
|
if dot is not None:
|
||||||
|
row.pack_start(dot, False, False, 0)
|
||||||
parent.pack_start(row, False, False, 0)
|
parent.pack_start(row, False, False, 0)
|
||||||
return e
|
return e
|
||||||
|
|
||||||
@ -743,6 +746,40 @@ class SettingsDialog:
|
|||||||
GLib.idle_add(self.llm_dot.set_markup, _dot(lc))
|
GLib.idle_add(self.llm_dot.set_markup, _dot(lc))
|
||||||
threading.Thread(target=check, daemon=True).start()
|
threading.Thread(target=check, daemon=True).start()
|
||||||
|
|
||||||
|
def _probe_dot(self, dot: Gtk.Label, uri: str, fallback_port: int) -> None:
|
||||||
|
"""Colour a connection dot by TCP reachability of a host:port URL (threaded).
|
||||||
|
|
||||||
|
Green = something is listening at the address, red = configured but
|
||||||
|
unreachable, grey = empty. Deliberately lightweight — the per-tab Test /
|
||||||
|
Connect buttons do the deeper, protocol-aware checks.
|
||||||
|
"""
|
||||||
|
uri = (uri or "").strip()
|
||||||
|
if not uri:
|
||||||
|
dot.set_markup(_dot(GREY))
|
||||||
|
return
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
p = urlparse(uri if "://" in uri else "//" + uri)
|
||||||
|
host = p.hostname or "127.0.0.1"
|
||||||
|
port = p.port or (443 if p.scheme == "https" else fallback_port)
|
||||||
|
|
||||||
|
def work():
|
||||||
|
import socket
|
||||||
|
try:
|
||||||
|
with socket.create_connection((host, port), timeout=2.0):
|
||||||
|
ok = True
|
||||||
|
except OSError:
|
||||||
|
ok = False
|
||||||
|
GLib.idle_add(dot.set_markup, _dot(GREEN if ok else RED))
|
||||||
|
threading.Thread(target=work, daemon=True).start()
|
||||||
|
|
||||||
|
def _on_ww_uri_leave(self, *_a) -> bool:
|
||||||
|
self._probe_dot(self.ww_dot, self.ww_uri.get_text(), 10400)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _on_tts_url_leave(self, *_a) -> bool:
|
||||||
|
self._probe_dot(self.tts_dot, self.wwb_url.get_text(), 80)
|
||||||
|
return False
|
||||||
|
|
||||||
# -- key binding ("Set" button captures the next keypress) ---
|
# -- key binding ("Set" button captures the next keypress) ---
|
||||||
def _key_field(self, page: Gtk.Box, label: str, value: str, placeholder: str = "", width: int = 150) -> Gtk.Entry:
|
def _key_field(self, page: Gtk.Box, label: str, value: str, placeholder: str = "", width: int = 150) -> Gtk.Entry:
|
||||||
row = Gtk.Box(spacing=10); row.set_margin_top(3); row.set_margin_bottom(3)
|
row = Gtk.Box(spacing=10); row.set_margin_top(3); row.set_margin_bottom(3)
|
||||||
@ -807,8 +844,14 @@ class SettingsDialog:
|
|||||||
page.pack_start(Gtk.Label(label="Hands-free (Wakeword)", xalign=0.0), False, False, 2)
|
page.pack_start(Gtk.Label(label="Hands-free (Wakeword)", xalign=0.0), False, False, 2)
|
||||||
self.ww_enabled = Gtk.Switch(); self.ww_enabled.set_active(self.cfg.wakeword_enabled); self.ww_enabled.set_halign(Gtk.Align.START)
|
self.ww_enabled = Gtk.Switch(); self.ww_enabled.set_active(self.cfg.wakeword_enabled); self.ww_enabled.set_halign(Gtk.Align.START)
|
||||||
_labeled(page, "Enable wakeword", self.ww_enabled)
|
_labeled(page, "Enable wakeword", self.ww_enabled)
|
||||||
self.ww_uri = _url_field(page, "Wyoming URI", "tcp://127.0.0.1:10400", self._ww_load)
|
self.ww_dot = Gtk.Label(); self.ww_dot.set_markup(_dot(GREY))
|
||||||
|
self.ww_dot.set_tooltip_text("Wakeword (Wyoming / openWakeWord) server: "
|
||||||
|
"green = reachable, red = unreachable")
|
||||||
|
self.ww_uri = _url_field(page, "Wakeword engine", "tcp://127.0.0.1:10400",
|
||||||
|
self._ww_load, dot=self.ww_dot)
|
||||||
self.ww_uri.set_text(self.cfg.wakeword_uri)
|
self.ww_uri.set_text(self.cfg.wakeword_uri)
|
||||||
|
self.ww_uri.connect("focus-out-event", self._on_ww_uri_leave)
|
||||||
|
self._probe_dot(self.ww_dot, self.cfg.wakeword_uri, 10400)
|
||||||
self.ww_model = _labeled(page, "Model name", _model_combo("Search models…"))
|
self.ww_model = _labeled(page, "Model name", _model_combo("Search models…"))
|
||||||
_fill_combo(self.ww_model, [], self.cfg.wakeword_model)
|
_fill_combo(self.ww_model, [], self.cfg.wakeword_model)
|
||||||
|
|
||||||
@ -962,6 +1005,8 @@ class SettingsDialog:
|
|||||||
self._meter.stop(); self._meter = None
|
self._meter.stop(); self._meter = None
|
||||||
|
|
||||||
def _ww_load(self) -> None:
|
def _ww_load(self) -> None:
|
||||||
|
self._probe_dot(self.ww_dot, self.ww_uri.get_text(), 10400)
|
||||||
|
|
||||||
def work():
|
def work():
|
||||||
import socket, json
|
import socket, json
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
@ -1090,8 +1135,13 @@ class SettingsDialog:
|
|||||||
|
|
||||||
# URL with a ⟳ reload that loads models + voices from the server (like the
|
# URL with a ⟳ reload that loads models + voices from the server (like the
|
||||||
# Engines tab). Set the saved URL after building the field.
|
# Engines tab). Set the saved URL after building the field.
|
||||||
self.wwb_url = _url_field(page, "TTS URL", "http://localhost:8880/v1", self._tts_reload)
|
self.tts_dot = Gtk.Label(); self.tts_dot.set_markup(_dot(GREY))
|
||||||
|
self.tts_dot.set_tooltip_text("TTS server: green = reachable, red = unreachable")
|
||||||
|
self.wwb_url = _url_field(page, "TTS URL", "http://localhost:8880/v1",
|
||||||
|
self._tts_reload, dot=self.tts_dot)
|
||||||
self.wwb_url.set_text(self.cfg.tts_url)
|
self.wwb_url.set_text(self.cfg.tts_url)
|
||||||
|
self.wwb_url.connect("focus-out-event", self._on_tts_url_leave)
|
||||||
|
self._probe_dot(self.tts_dot, self.cfg.tts_url, 80)
|
||||||
self.wwb_key = _labeled(
|
self.wwb_key = _labeled(
|
||||||
page, "API key env", _entry(self.cfg.tts_api_key_env, placeholder="(optional, e.g. OPENAI_API_KEY)"),
|
page, "API key env", _entry(self.cfg.tts_api_key_env, placeholder="(optional, e.g. OPENAI_API_KEY)"),
|
||||||
tooltip="Name of an environment variable holding a bearer token. Leave empty for "
|
tooltip="Name of an environment variable holding a bearer token. Leave empty for "
|
||||||
@ -1183,6 +1233,7 @@ class SettingsDialog:
|
|||||||
if not url:
|
if not url:
|
||||||
self._error("Enter the TTS server URL first (e.g. http://localhost:8880/v1).")
|
self._error("Enter the TTS server URL first (e.g. http://localhost:8880/v1).")
|
||||||
return
|
return
|
||||||
|
self._probe_dot(self.tts_dot, url, 80)
|
||||||
self.wwb_test_lbl.set_markup("<i>Loading models & voices…</i>")
|
self.wwb_test_lbl.set_markup("<i>Loading models & voices…</i>")
|
||||||
|
|
||||||
def work():
|
def work():
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user