diff options
| author | historia <historiavg@proton.me> | 2026-08-27 17:03:57 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-08-27 17:03:57 -0400 |
| commit | cef2352a5e81b272d067c2c02eb9588e54edfcfd (patch) | |
| tree | 83af416a82d9e854f44b6f3207eb8ea648b0896d /app/ui | |
| parent | 6527240aa69a08f06e36e796818721abeec9f592 (diff) | |
| download | tts-audiobook-generator-cef2352a5e81b272d067c2c02eb9588e54edfcfd.tar.gz | |
feat: automatic updates added to configure backend menu
Diffstat (limited to 'app/ui')
| -rw-r--r-- | app/ui/hub.py | 69 |
1 files changed, 64 insertions, 5 deletions
diff --git a/app/ui/hub.py b/app/ui/hub.py index 9c1bd97..a57ab6c 100644 --- a/app/ui/hub.py +++ b/app/ui/hub.py @@ -154,7 +154,9 @@ class _Hub: separated from the rest by a blank line. The remaining actions are populated from the detected statuses: configure each configurable backend (qwen offers its per-model weight (un)installer there), - install (backends with nothing on disk), and uninstall. + install (backends with nothing on disk), update (every installed + backend refreshed to the latest upstream version in one task-view + run), and uninstall. Selecting one pushes the next screen; Esc pops back to the main menu. The Build action downloads any missing models alongside the build (a split view), so it heals a configured-but-unbuilt backend @@ -202,13 +204,16 @@ class _Hub: for info in installed if _configurable(info)] if any(_installable(info, by_key) for info in REGISTRY): options.append(("Install Backend", "install")) + if any(_updatable(info, by_key) for info in REGISTRY): + options.append(("Update backends", "update")) if any(_uninstallable(info, by_key) for info in REGISTRY): options.append(("Uninstall Backend", "uninstall")) choice = tui.menu( self.stdscr, "Configure backends", options, back_value=tui.Wizard.BACK, - help_lines=["Install, configure, or remove a TTS backend."], + help_lines=["Install, update, configure, or remove a TTS " + "backend."], table_title="Backend status", table_rows=_status_rows(statuses), notice_lines=_notice_lines()) @@ -216,6 +221,10 @@ class _Hub: return tui.Wizard.BACK if choice == "install": return self.screen_install + if choice == "update": + _update_backends_action(self.stdscr) + invalidate_detect_cache() + continue # an inline action: re-show this same menu if choice == "uninstall": return self.screen_uninstall if choice == "download_models": @@ -644,6 +653,16 @@ def _uninstallable(info, by_key: dict) -> bool: return status is not None and status.installed +def _updatable(info, by_key: dict) -> bool: + """True when the "Update backends" action has something to do for INFO. + + The same on-disk predicate as _uninstallable — update acts on exactly + what uninstall removes (the pip package / the checkout) — plus the + backend must implement an update action at all. + """ + return info.update is not None and _uninstallable(info, by_key) + + def _download_models_action(stdscr) -> None: """Run the "Download Missing Models (audio.cpp)" action inside the TUI. @@ -695,6 +714,45 @@ def _download_models_action(stdscr) -> None: "err") +def _update_backends_action(stdscr) -> None: + """Run the "Update backends" action inside the TUI. + + One task-view step per installed backend that implements update, in + registry order; each update stops its managed server first (best- + effort) and then refreshes — pip install -U for the pip backends, + git fetch + hard reset for the checkouts, with audio.cpp's binary + rebuilt when its checkout moved. A failing backend's step is marked + [FAIL] and the remaining backends still update (the run's exit code + is the first failure). A flash summarizes the result; the status + table re-detects when the menu re-shows. + """ + statuses = detect_all() + by_key = {st.key: st for st in statuses} + targets = [info for info in REGISTRY if _updatable(info, by_key)] + if not targets: + tui.flash(stdscr, "No installed backend supports updating.") + return + + def make_work(info): + def work(emit, cancel): + return info.update(emit=emit, cancel=cancel) + return work + + steps = [taskview.TaskStep(f"Update {info.label}", make_work(info)) + for info in targets] + rc = taskview.run_steps(stdscr, "Update backends", steps) + if rc == 0: + tui.flash(stdscr, "Every backend is up to date (or just " + "updated).", "ok") + elif rc == 130: + tui.flash(stdscr, "Update cancelled — re-run 'Update backends' " + "any time.", "warn") + else: + tui.flash(stdscr, "Some updates did not complete (failed or " + "cancelled) — see the log above. Re-run 'Update " + "backends' to retry.", "err") + + def _status_mark(status: Optional[BackendStatus]) -> Tuple[str, str, str]: """Map a backend's state to (status_text, status_kind, name_kind). @@ -705,8 +763,9 @@ def _status_mark(status: Optional[BackendStatus]) -> Tuple[str, str, str]: "running [local, remote]". Otherwise a backend set up only part-way (``status.partial``) shows that label verbatim (amber), e.g. audio.cpp's "downloaded (not built)" or "built (not configured)"; 'installed' - (orange/warn) when the backend is present on disk; or 'unavailable' - (red/err). A backend that is neither installed nor running is unusable, + (green/ok) when the backend is present on disk — amber (warn) instead + when its models are missing — or 'unavailable' (red/err). A backend + that is neither installed nor running is unusable, so its name is dimmed (NAME_KIND). A multi-model backend (qwen) also names which models answered in parentheses, e.g. "running [local, remote] (Base, @@ -734,7 +793,7 @@ def _status_mark(status: Optional[BackendStatus]) -> Tuple[str, str, str]: if status is not None and status.installed: if status.models_missing and not status.running: return ("installed (models missing)", "warn", "body") - return ("installed", "warn", "body") + return ("installed", "ok", "body") return ("unavailable", "err", "dim") |
