diff options
Diffstat (limited to 'app/ui')
| -rw-r--r-- | app/ui/hub.py | 70 |
1 files changed, 68 insertions, 2 deletions
diff --git a/app/ui/hub.py b/app/ui/hub.py index c003b16..67680db 100644 --- a/app/ui/hub.py +++ b/app/ui/hub.py @@ -30,6 +30,7 @@ from pathlib import Path from typing import Callable, Optional, Tuple import logging_kit +import selfupdate from backends import ( REGISTRY, @@ -170,7 +171,9 @@ class _Hub: backend (qwen offers its per-model weight (un)installer there), start/stop the installed backends' local servers, install (backends with nothing on disk), update (every installed backend refreshed to - the latest upstream version in one task-view run), and uninstall. + the latest upstream version in one task-view run), update the + generator itself (git checkout moved to the remote's HEAD, keeping + locally modified files such as config.py), 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 @@ -222,6 +225,8 @@ class _Hub: options.append(("Install Backend", "install")) if any(_updatable(info, by_key) for info in REGISTRY): options.append(("Update Backends", "update")) + if selfupdate.is_git_checkout(): + options.append(("Update Generator", "update_self")) if any(_uninstallable(info, by_key) for info in REGISTRY): options.append(("Uninstall Backend", "uninstall")) @@ -229,7 +234,8 @@ class _Hub: self.stdscr, "Configure Backends", options, back_value=tui.Wizard.BACK, help_lines=["Install, update, configure, or remove a TTS " - "backend."], + "backend. 'Update Generator' refreshes this " + "tool's own checkout instead."], table_rows=_status_rows(statuses), notice_lines=_notice_lines()) if choice is tui.Wizard.BACK: @@ -242,6 +248,10 @@ class _Hub: _update_backends_action(self.stdscr) invalidate_detect_cache() continue # an inline action: re-show this same menu + if choice == "update_self": + _update_self_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": @@ -759,6 +769,62 @@ def _update_backends_action(stdscr) -> None: "Backends' to retry.", "err") +def _update_self_action(stdscr) -> None: + """Run the "Update Generator" action inside the TUI. + + Moves the generator's own git checkout to the remote's default-branch + HEAD (fetch + hard reset — the same flow the backend checkouts use). + User state cannot be overwritten: everything untracked (app/envs, + input/output/voices, the backend checkouts, logs) is outside the + reset's reach, and locally modified tracked files — the user-editable + app/converter/config.py above all — are snapshotted before the reset + and written back afterwards (a kept file whose upstream version + changed is reported for a manual merge). A dirty checkout confirms + first (declining, Esc included, aborts before anything runs); the run + itself streams in the task view like the other inline actions. After: + a successful run flashes the moved commit range (or "already up to + date") plus the restart reminder — the fresh code loads on relaunch, + where requirements re-install runs automatically — while cancel and + failure flash their usual warn/err. The menu re-shows either way. + """ + if not selfupdate.is_git_checkout(): + tui.flash(stdscr, "This install is not a git checkout — update by " + "re-cloning the repository.", "err") + return + before = selfupdate.current_commit() + modified = selfupdate.modified_tracked_files() + if modified: + body = ["These locally modified files are kept as they are:", + ""] + body += modified[:6] + if len(modified) > 6: + body.append(f"...and {len(modified) - 6} more") + if tui.confirm(stdscr, "Update the generator?", body=body, + default=False, cancel_value=False) is not True: + return + + def work(emit, cancel): + return selfupdate.update_generator(emit=emit, cancel=cancel) + + rc = taskview.run_steps( + stdscr, "Update Generator", + [taskview.TaskStep("Fetch and reset the checkout", work)]) + after = selfupdate.current_commit() + if rc == 130: + tui.flash(stdscr, "Update cancelled — re-run 'Update Generator' " + "any time.", "warn") + elif rc: + tui.flash(stdscr, "The generator update did not complete — see the " + "log above and retry.", "err") + elif before is not None and before == after: + tui.flash(stdscr, f"The generator is already up to date ({before}).", + "ok") + else: + tui.flash(stdscr, f"Generator updated {before or '?'} → {after}. " + "Restart to apply — requirements re-install runs " + "automatically on next launch.", "ok") + + def _status_mark(status: Optional[BackendStatus]) -> Tuple[str, str, str]: """Map a backend's state to (status_text, status_kind, name_kind). |
