From 6ccb6d443d2fb871b43d96ea61a95bc3e6a92355 Mon Sep 17 00:00:00 2001 From: historia Date: Wed, 26 Aug 2026 17:46:48 -0400 Subject: feat: combined install/configure tui screens into one menu, removed extraneous wizard screens --- app/backends/audiocpp/models.py | 128 +++++++++++++++++++++++++++++----------- 1 file changed, 93 insertions(+), 35 deletions(-) (limited to 'app/backends/audiocpp/models.py') diff --git a/app/backends/audiocpp/models.py b/app/backends/audiocpp/models.py index 4e6b8bb..75bc06a 100644 --- a/app/backends/audiocpp/models.py +++ b/app/backends/audiocpp/models.py @@ -6,22 +6,79 @@ import shutil import sys import tempfile from pathlib import Path -from typing import Callable, Dict, List, Optional, Set, Tuple +from typing import Dict, List, Optional, Set, Tuple from backends import common from . import catalog as _catalog +def _installed_display_names(audiocpp_dir: Path, + model_entries: Optional[List[dict]], + install_guidance: List[Tuple[str, str]] + ) -> Set[str]: + """Display names from INSTALL_GUIDANCE whose model files are on disk. + + MODEL_ENTRIES and INSTALL_GUIDANCE are built in lockstep by + ``_build_entries`` (one guidance pair per entry), so the pairs resolve + positionally: each entry's ``path`` is checked against the checkout + exactly like ``_all_models_present`` resolves it. Returns an empty set + when ENTRIES is None or does not line up with the guidance (no + filtering — every model counts as not installed). + """ + if model_entries is None or len(model_entries) != len(install_guidance): + return set() + installed: Set[str] = set() + for entry, (name, _install_id) in zip(model_entries, install_guidance): + rel = entry.get("path") + if not isinstance(rel, str) or not rel: + continue + path = Path(rel) if Path(rel).is_absolute() else audiocpp_dir / rel + if _model_path_present(path): + installed.add(name) + return installed + + +def _split_pending_and_installed( + install_guidance: List[Tuple[str, str]], + installed_names: Set[str]) -> Tuple[List[Tuple[str, str]], List[str]]: + """Partition guidance into (pending installs, installed display names). + + PENDING keeps only models whose display name is not INSTALLED_NAMES, + de-duped by install id (the same package may host several entries) in + first-occurrence order. INSTALLED lists each installed display name + once, also in first-occurrence order. + """ + seen: Set[str] = set() + pending: List[Tuple[str, str]] = [] + noted: List[str] = [] + for name, install_id in install_guidance: + if name in installed_names: + if name not in noted: + noted.append(name) + continue + if install_id in seen: + continue + seen.add(install_id) + pending.append((name, install_id)) + return pending, noted + + def _install_models(audiocpp_dir: Path, install_guidance: List[Tuple[str, str]], - download: bool, emit=None, cancel=None) -> int: - """Print and optionally run the model install commands. - - One ``python install `` command per hosted model (de-duped - by install id). When DOWNLOAD is True each command is run in the audio.cpp + download: bool, emit=None, cancel=None, + model_entries: Optional[List[dict]] = None) -> int: + """Report and optionally run the model install commands. + + When MODEL_ENTRIES (built in lockstep with INSTALL_GUIDANCE by + ``_build_entries``) is given, models already on disk are reported as + installed and never re-downloaded or printed as commands; when every + selected model is present nothing runs at all. The remaining models + get one ``python install `` command each (de-duped by + install id). When DOWNLOAD is True each command is run in the audio.cpp checkout via ``subprocess`` so the models are downloaded automatically; a failing install is reported as a warning and does not abort the remaining downloads. When DOWNLOAD is False (or the model manager is - missing) the commands are only printed, copy-pasteable as before. + missing) the commands are only printed after a note that setup downloads + them automatically — copy-pasteable for a manual install. With EMIT given (the in-TUI task view) each download streams its output to EMIT and — when the checkout's ``model_manager_v2.py`` supports it — @@ -31,13 +88,13 @@ def _install_models(audiocpp_dir: Path, Returns 0 when every command succeeded (or nothing needed running), 130 when cancelled, 1 when any download failed. """ + if not install_guidance: + return 0 manager = audiocpp_dir / "tools" / "model_manager_v2.py" - seen: Set[str] = set() - install_ids: List[str] = [] - for _, install_id in install_guidance: - if install_id not in seen: - seen.add(install_id) - install_ids.append(install_id) + installed_names = _installed_display_names( + audiocpp_dir, model_entries, install_guidance) + pending, installed_noted = _split_pending_and_installed( + install_guidance, installed_names) supports_progress = emit is not None and _manager_supports_progress(manager) @@ -46,12 +103,21 @@ def _install_models(audiocpp_dir: Path, "instead of running them") download = False + for name in installed_noted: + print(f"[OK] {name} is already installed.") + if not pending: + print("[OK] All selected models are already installed.") + return 0 + + if not download: + print("[INFO] Models are downloaded automatically by this tool's " + "setup — to download them manually instead, run:") + for _, install_id in pending: + print(f"python {manager} install {install_id}") + return 0 + failed = False - for install_id in install_ids: - command = f"python {manager} install {install_id}" - if not download: - print(command) - continue + for _, install_id in pending: print(f"[INFO] Downloading {install_id}...") argv = [sys.executable, str(manager), "install", install_id] cancel_file: Optional[Path] = None @@ -69,7 +135,8 @@ def _install_models(audiocpp_dir: Path, argv, cwd=str(audiocpp_dir), emit=emit, cancel=cancel, on_cancel=on_cancel) except OSError as exc: - print(f"[WARNING] Could not run {command}: {exc}") + print(f"[WARNING] Could not run python {manager} install " + f"{install_id}: {exc}") rc = 1 finally: if cancel_file is not None: @@ -101,27 +168,18 @@ def _manager_supports_progress(manager: Path) -> bool: return "AUDIOCPP_PROGRESS" in text and "--cancel-file" in text -def _decide_download(audiocpp_dir: Path, - model_entries: List[dict], - confirm: Callable[[str, bool], bool]) -> bool: - """Ask whether to download the selected models now. +def download_applicable(audiocpp_dir: Path, model_entries: List[dict]) -> bool: + """True when the wizard's "download models automatically?" row applies. - CONFIRM asks the yes/no question (ask_bool for the line prompts, a TUI - confirm for the wizard). When the audio.cpp model manager is missing the - prompt is skipped and False is returned, so the install commands are only - printed rather than offered to run. The prompt is also skipped (False) - when every selected model is already on disk (see ``_all_models_present``), - so an already-configured checkout is not asked to re-download models it - already has. + The audio.cpp model manager must be present (otherwise the install + commands can only be printed), and at least one selected model must be + missing from disk (see ``_all_models_present``), so an already-configured + checkout is not asked to re-download models it already has. """ manager = audiocpp_dir / "tools" / "model_manager_v2.py" if not manager.is_file(): return False - if _all_models_present(audiocpp_dir, model_entries): - return False - return confirm( - "Automatically download the selected models with model_manager_v2.py " - "now?", True) + return not _all_models_present(audiocpp_dir, model_entries) def _build_tree_families(catalog: List[dict]) -> List[dict]: -- cgit v1.2.3