aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rwxr-xr-xapp/backends/audiocpp.py101
-rwxr-xr-xapp/backends/faster.py35
-rw-r--r--app/backends/qwen.py19
-rw-r--r--app/tests/test_backends_audiocpp.py34
-rw-r--r--app/tests/test_tui.py71
-rw-r--r--app/ui/tui.py21
6 files changed, 30 insertions, 251 deletions
diff --git a/app/backends/audiocpp.py b/app/backends/audiocpp.py
index feeb22a..d4c79b0 100755
--- a/app/backends/audiocpp.py
+++ b/app/backends/audiocpp.py
@@ -94,7 +94,7 @@ AUDIOCPP_DIR_NAME = "audio.cpp"
AUDIOCPP_GIT_URL = "https://github.com/0xShug0/audio.cpp"
# Sentinel returned by tui.confirm (via its cancel_value) when the user
-# presses Esc on an overwrite prompt to go back to the checkout browser
+# presses Esc on an overwrite prompt to go back to the wav-directory browser
# instead of aborting the wizard.
_GO_BACK = object()
@@ -161,38 +161,6 @@ def _resolve_audiocpp_root(directory: Path) -> Optional[Path]:
return None
-def _audiocpp_root_status(directory: Path) -> Tuple[str, str]:
- """TUI status describing the directory listed in the checkout browser."""
- if _resolve_audiocpp_root(directory) is not None:
- return ("model_specs/ found here", "ok")
- return ("No model_specs/ directory here", "warn")
-
-
-def _audiocpp_root_preview(directory: Path) -> Optional[Tuple[str, str]]:
- """TUI status for a highlighted subdirectory in the checkout browser."""
- if (directory / "model_specs").is_dir():
- return ("contains model_specs/", "ok")
- return None
-
-
-def _checkout_auto_select(entry: Path) -> Optional[Path]:
- """Auto-accept a highlighted checkout in the TUI browser.
-
- A subdirectory named ``audio.cpp`` that already contains a
- ``model_specs`` directory is the audio.cpp checkout root, so it is
- accepted immediately on Enter/Right (as if ``[ Use this directory ]``
- had been pressed) instead of being descended into. Anything else
- returns None so the user keeps browsing. This is only consulted
- while auto-accepting is still enabled; after the user presses Esc to
- go back, the browser is restarted inside the previously accepted
- checkout and this callback is no longer passed, so a wrong guess can
- be corrected.
- """
- if entry.name == "audio.cpp" and (entry / "model_specs").is_dir():
- return entry
- return None
-
-
# Backend display order, with short descriptions. The backend name is padded
# so the descriptions' dashes line up in the menu.
_BACKEND_DESCRIPTIONS = (
@@ -1058,54 +1026,6 @@ def _wizard(stdscr, args: argparse.Namespace, parser: argparse.ArgumentParser
"unused_entries": s["unused_entries"],
}
- def screen_no_checkout():
- """First screen when no checkout exists: clone or browse.
-
- No ``back_value``: Esc aborts the whole wizard (nothing before it).
- """
- choice = tui.menu(
- stdscr, "No audio.cpp checkout found",
- [(f"Clone into ./app/{AUDIOCPP_DIR_NAME} "
- f"(from {AUDIOCPP_GIT_URL})", "clone"),
- ("Browse for an existing checkout", "browse")],
- help_lines=[
- "audio.cpp hosts the TTS model families "
- "this generator uses.",
- "Clone it into the project's app "
- "directory, or point at an existing "
- "checkout."])
- if choice == "clone":
- target = APP_DIR / AUDIOCPP_DIR_NAME
- with tui.suspend(stdscr):
- rc = common.git_clone(AUDIOCPP_GIT_URL, target)
- if rc != 0:
- raise _TuiError(
- f"git clone failed (exit {rc}). Clone "
- f"audio.cpp manually: git clone "
- f"{AUDIOCPP_GIT_URL} {target}")
- resolve_checkout(target)
- else:
- return screen_browse_checkout
- return _after_families()
-
- def screen_browse_checkout():
- """Browse for an existing checkout (Esc returns to the clone menu)."""
- audiocpp_dir = tui.browse_directory(
- stdscr, "Select your audio.cpp directory",
- validate=lambda p: None if _resolve_audiocpp_root(p)
- else "No model_specs/ directory here",
- info=_audiocpp_root_status,
- preview=_audiocpp_root_preview,
- help_lines=["The root folder of your audio.cpp checkout;",
- "it is the one that contains model_specs/"],
- start=Path.cwd(),
- auto_select=_checkout_auto_select,
- back_value=_GO_BACK)
- if audiocpp_dir is _GO_BACK:
- return tui.Wizard.BACK
- resolve_checkout(audiocpp_dir)
- return _after_families()
-
def screen_families():
"""Pick TTS model families and packages (the modify tree)."""
tree_families = _build_tree_families(s["catalog"])
@@ -1385,15 +1305,24 @@ def _wizard(stdscr, args: argparse.Namespace, parser: argparse.ArgumentParser
return _finalize()
# First screen: resolve the checkout directly when it already exists
- # (the modify flow), so the wizard starts on a real screen.
+ # (the modify flow), so the wizard starts on a real screen. When no
+ # checkout exists, clone it into ./app/audio.cpp without asking, then
+ # continue the same way.
audiocpp_dir = args.audiocpp_dir
if audiocpp_dir is None:
audiocpp_dir = find_local_checkout()
if audiocpp_dir is None:
- first = screen_no_checkout
- else:
- resolve_checkout(audiocpp_dir)
- first = _after_families()
+ target = APP_DIR / AUDIOCPP_DIR_NAME
+ with tui.suspend(stdscr):
+ rc = common.git_clone(AUDIOCPP_GIT_URL, target)
+ if rc != 0:
+ raise _TuiError(
+ f"git clone failed (exit {rc}). Clone "
+ f"audio.cpp manually: git clone "
+ f"{AUDIOCPP_GIT_URL} {target}")
+ audiocpp_dir = target
+ resolve_checkout(audiocpp_dir)
+ first = _after_families()
return tui.Wizard().run(first)
diff --git a/app/backends/faster.py b/app/backends/faster.py
index e209ff2..36121ff 100755
--- a/app/backends/faster.py
+++ b/app/backends/faster.py
@@ -225,30 +225,11 @@ def _wizard(stdscr, args: argparse.Namespace) -> Optional[dict]:
wav_start = next(iter(ref_dirs))
s["wav_start"] = wav_start
- def screen_install():
- choice = tui.confirm(stdscr, "faster-qwen3-tts is not installed. "
- "pip install it now?", default=True,
- cancel_value=_GO_BACK)
- if choice is _GO_BACK:
- return tui.Wizard.BACK
- s["do_install"] = choice
- return _after_install()
-
- def _after_install():
- if not _is_cloned() and not args.skip_clone:
- return screen_clone
- s["do_clone"] = False
- return _after_clone()
-
- def screen_clone():
- choice = tui.confirm(
- stdscr, f"faster-qwen3-tts repo not cloned. Clone it into "
- f"./app/{FASTER_DIR_NAME}?", default=True,
- cancel_value=_GO_BACK)
- if choice is _GO_BACK:
- return tui.Wizard.BACK
- s["do_clone"] = choice
- return _after_clone()
+ # Install and clone happen without asking: when the package or repo is
+ # missing (and not skipped by flag), the wizard just does it and moves
+ # to the next screen.
+ s["do_install"] = (not _is_installed()) and not args.skip_install
+ s["do_clone"] = (not _is_cloned()) and not args.skip_clone
def _after_clone():
if args.input_dir is None:
@@ -361,11 +342,7 @@ def _wizard(stdscr, args: argparse.Namespace) -> Optional[dict]:
"plan": s["plan"],
}
- if not _is_installed() and not args.skip_install:
- first = screen_install
- else:
- first = _after_install()
- return tui.Wizard().run(first)
+ return tui.Wizard().run(_after_clone())
def _try_language(value: str) -> bool:
diff --git a/app/backends/qwen.py b/app/backends/qwen.py
index 3416ebe..f1e7c79 100644
--- a/app/backends/qwen.py
+++ b/app/backends/qwen.py
@@ -71,15 +71,6 @@ def _wizard(stdscr, args: argparse.Namespace) -> Optional[dict]:
_GO_BACK = object()
s: dict = {}
- def screen_install():
- choice = tui.confirm(stdscr, "qwen-tts is not installed. "
- "pip install it now?", default=True,
- cancel_value=_GO_BACK)
- if choice is _GO_BACK:
- return tui.Wizard.BACK
- s["do_install"] = choice
- return _after_install()
-
def _after_install():
if args.port_custom is None:
return screen_custom_port
@@ -145,11 +136,11 @@ def _wizard(stdscr, args: argparse.Namespace) -> Optional[dict]:
"speaker": s["speaker"],
}
- if not _is_installed() and not args.skip_install:
- first = screen_install
- else:
- first = _after_install()
- return tui.Wizard().run(first)
+ # pip install happens without asking: when the package is missing (and
+ # not skipped by flag), the wizard just does it and moves to the next
+ # screen.
+ s["do_install"] = (not _is_installed()) and not args.skip_install
+ return tui.Wizard().run(_after_install())
def _execute(settings: dict) -> int:
diff --git a/app/tests/test_backends_audiocpp.py b/app/tests/test_backends_audiocpp.py
index dd19cd5..b724c0d 100644
--- a/app/tests/test_backends_audiocpp.py
+++ b/app/tests/test_backends_audiocpp.py
@@ -370,40 +370,6 @@ class NormalizeDirArgTests(unittest.TestCase):
self.assertEqual(result, Path("/tmp/foo").resolve())
-class CheckoutAutoSelectTests(unittest.TestCase):
- """TUI browser auto-accept callback for an audio.cpp checkout."""
-
- def setUp(self):
- self._td = tempfile.TemporaryDirectory()
- self.root = Path(self._td.name)
-
- def tearDown(self):
- self._td.cleanup()
-
- def test_accepts_audio_cpp_containing_model_specs(self):
- checkout = self.root / "audio.cpp"
- checkout.mkdir()
- (checkout / "model_specs").mkdir()
- self.assertEqual(make_server._checkout_auto_select(checkout),
- checkout)
-
- def test_rejects_audio_cpp_without_model_specs(self):
- checkout = self.root / "audio.cpp"
- checkout.mkdir()
- self.assertIsNone(make_server._checkout_auto_select(checkout))
-
- def test_rejects_other_name_even_with_model_specs(self):
- other = self.root / "not-audiocpp"
- other.mkdir()
- (other / "model_specs").mkdir()
- self.assertIsNone(make_server._checkout_auto_select(other))
-
- def test_rejects_plain_directory(self):
- plain = self.root / "somewhere"
- plain.mkdir()
- self.assertIsNone(make_server._checkout_auto_select(plain))
-
-
class DefaultModelIdTests(unittest.TestCase):
def test_preferred_ids_for_tested_families(self):
self.assertEqual(make_server.default_model_id("qwen3_tts"), "qwen")
diff --git a/app/tests/test_tui.py b/app/tests/test_tui.py
index b206de7..d5cbb7e 100644
--- a/app/tests/test_tui.py
+++ b/app/tests/test_tui.py
@@ -687,13 +687,6 @@ class FormTests(TuiTestCase):
self.assertEqual(result, {"first": "a", "second": "bx"})
-def _accept_audio_cpp(entry: Path):
- """auto_select callback that accepts an 'audio.cpp' checkout root."""
- if entry.name == "audio.cpp" and (entry / "model_specs").is_dir():
- return entry
- return None
-
-
class BrowseDirectoryTests(TuiTestCase):
def setUp(self):
super().setUp()
@@ -704,16 +697,6 @@ class BrowseDirectoryTests(TuiTestCase):
(self.root / name).mkdir()
(self.root / "noise.txt").write_text("x", encoding="utf-8")
- def _checkout_tree(self):
- """A temp dir containing an 'audio.cpp' checkout + a sibling dir."""
- tmp = tempfile.TemporaryDirectory()
- self.addCleanup(tmp.cleanup)
- root = Path(tmp.name)
- (root / "audio.cpp").mkdir()
- (root / "audio.cpp" / "model_specs").mkdir()
- (root / "other").mkdir()
- return root
-
def test_listing_rows_left_justified(self):
screen = FakeScreen(keys=[10])
chosen = tui.browse_directory(screen, "Pick", start=self.root)
@@ -735,60 +718,6 @@ class BrowseDirectoryTests(TuiTestCase):
self.assertEqual(chosen, (self.root / "alpha").resolve())
self.assert_inside_border(screen)
- def test_enter_auto_accepts_matching_subdir(self):
- root = self._checkout_tree()
- # sel 0 = [ Use this directory ], 1 = .., 2 = audio.cpp/
- keys = [FakeCurses.KEY_DOWN, FakeCurses.KEY_DOWN, 10]
- screen = FakeScreen(keys=keys)
- chosen = tui.browse_directory(screen, "Pick", start=root,
- auto_select=_accept_audio_cpp)
- self.assertEqual(chosen, (root / "audio.cpp").resolve())
-
- def test_right_auto_accepts_matching_subdir(self):
- root = self._checkout_tree()
- keys = [FakeCurses.KEY_DOWN, FakeCurses.KEY_DOWN,
- FakeCurses.KEY_RIGHT]
- screen = FakeScreen(keys=keys)
- chosen = tui.browse_directory(screen, "Pick", start=root,
- auto_select=_accept_audio_cpp)
- self.assertEqual(chosen, (root / "audio.cpp").resolve())
-
- def test_use_this_directory_ignores_auto_select(self):
- # Enter on '[ Use this directory ]' must accept the listed dir
- # without ever consulting auto_select.
- root = self._checkout_tree()
- calls = []
-
- def callback(entry):
- calls.append(entry)
- return entry # would auto-accept any subdir if consulted
-
- screen = FakeScreen(keys=[10])
- chosen = tui.browse_directory(screen, "Pick", start=root,
- auto_select=callback)
- self.assertEqual(chosen, root.resolve())
- self.assertEqual(calls, [])
-
- def test_auto_select_returning_none_descends_normally(self):
- # A non-matching subdir (or a None reply) keeps browsing: Enter
- # descends into it, then '[ Use this directory ]' accepts it.
- root = self._checkout_tree()
- calls = []
-
- def callback(entry):
- calls.append(entry)
- return None
-
- # sel 0 = use, 1 = .., 2 = audio.cpp/, 3 = other/
- keys = [FakeCurses.KEY_DOWN, FakeCurses.KEY_DOWN,
- FakeCurses.KEY_DOWN, 10, 10]
- screen = FakeScreen(keys=keys)
- chosen = tui.browse_directory(screen, "Pick", start=root,
- auto_select=callback)
- self.assertEqual(chosen, (root / "other").resolve())
- # auto_select was consulted only for the highlighted 'other/' row.
- self.assertEqual([p.name for p in calls], ["other"])
-
def test_esc_returns_back_value(self):
marker = object()
screen = FakeScreen(keys=[27])
diff --git a/app/ui/tui.py b/app/ui/tui.py
index 0a119e9..f1abea1 100644
--- a/app/ui/tui.py
+++ b/app/ui/tui.py
@@ -1002,8 +1002,6 @@ def browse_directory(scr, title: str,
preview: Optional[Callable[[Path],
Optional[Tuple[str, str]]]] = None,
help_lines: Optional[Sequence[str]] = None,
- auto_select: Optional[Callable[
- [Path], Optional[Path]]] = None,
back_value: object = None
) -> Path:
"""Pick a directory DOS-browser style.
@@ -1023,15 +1021,9 @@ def browse_directory(scr, title: str,
listed directory's path — kind is "ok" (green), "warn" (yellow),
"err" (red), "info" (dim) or "input". PREVIEW(directory) returns
one for the highlighted subdirectory, shown on the status line.
- AUTO_SELECT receives a highlighted subdirectory when the user
- opens it (Enter, Right or 'l') and may return a Path to accept
- immediately — as if '[ Use this directory ]' had been pressed on
- it — instead of descending; returning None keeps browsing. This
- lets a subdirectory that already looks like the target (e.g. an
- 'audio.cpp' checkout containing 'model_specs/') be picked in one
- keystroke. Esc (or 'q') aborts the wizard unless BACK_VALUE is given
- (not None), in which case either key returns it so the caller can
- fall back a screen.
+ Esc (or 'q') aborts the wizard unless BACK_VALUE is given (not
+ None), in which case either key returns it so the caller can fall
+ back a screen.
"""
footer = ("Up/Down = move Enter = open/use Left = parent "
"e = type path Esc = cancel")
@@ -1119,12 +1111,7 @@ def browse_directory(scr, title: str,
highlight = current
current = current.parent
else:
- entry = entries[sel - offset]
- if auto_select is not None:
- picked = auto_select(entry)
- if picked is not None:
- return picked
- current = entry
+ current = entries[sel - offset]
sel = 0
elif key in (curses.KEY_LEFT, ord("h"), ord("u"),
curses.KEY_BACKSPACE, 8, 127):