aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/tests/test_hub.py70
-rw-r--r--app/ui/hub.py26
2 files changed, 51 insertions, 45 deletions
diff --git a/app/tests/test_hub.py b/app/tests/test_hub.py
index 3550f5b..daa4abd 100644
--- a/app/tests/test_hub.py
+++ b/app/tests/test_hub.py
@@ -195,7 +195,7 @@ class HubMenuTests(unittest.TestCase):
self.assertEqual(labels,
["Configure Backends", "Settings", "Help", "Quit"])
- def test_menu_has_all_five_when_one_installed(self):
+ def test_menu_lists_convert_when_one_installed(self):
captured = {}
def fake_menu(stdscr, title, options, **kwargs):
@@ -212,8 +212,8 @@ class HubMenuTests(unittest.TestCase):
labels = [label for label, _ in captured["options"]]
self.assertEqual(
labels,
- ["Generate Audiobooks", "Configure Backends",
- "Start/Stop Backend Servers", "Settings", "Help", "Quit"])
+ ["Generate Audiobooks", "Configure Backends", "Settings",
+ "Help", "Quit"])
# The status table is passed through, one row per backend.
self.assertEqual(captured["rows"],
[("qwen-tts", "installed", "ok", "body")])
@@ -241,9 +241,10 @@ class HubMenuTests(unittest.TestCase):
[("audio.cpp", "unavailable", "err", "dim"),
("qwen-tts", "running [remote]", "ok", "body")])
- def test_menu_hides_server_when_only_running(self):
+ def test_server_menu_hidden_from_main_when_only_running(self):
# Running but not installed (an external server) still unlocks
- # Convert — but Start/Stop needs the backend on this machine.
+ # Convert — but Start/Stop needs the backend on this machine, and
+ # lives in Configure Backends.
captured = {}
def fake_menu(stdscr, title, options, **kwargs):
@@ -297,7 +298,7 @@ class HubMenuTests(unittest.TestCase):
# Installed-but-not-ready backends → Convert is offered, but the
# convert flow has nothing to list: it flashes a hint (no "Configure
# Backends" detour anymore) and returns to the main menu. Then
- # quit: 6 main-menu options, Quit is the 6th (Down x5).
+ # quit: 5 main-menu options, Quit is the 5th (Down x4).
from backends import BackendStatus
statuses = [BackendStatus("audiocpp", "audio.cpp", installed=True,
configured=False),
@@ -312,11 +313,10 @@ class HubMenuTests(unittest.TestCase):
with patch.object(hub, "detect_all", return_value=statuses), \
patch.object(hub.tui, "flash", fake_flash):
- # Convert(Enter) → flash → main menu; Down x5 -> Quit, Enter.
+ # Convert(Enter) → flash → main menu; Down x4 -> Quit, Enter.
screen = FakeScreen(keys=[10,
FakeCurses.KEY_DOWN, FakeCurses.KEY_DOWN,
FakeCurses.KEY_DOWN, FakeCurses.KEY_DOWN,
- FakeCurses.KEY_DOWN,
10])
result = hub._Hub(screen).run()
self.assertIsNone(result)
@@ -430,13 +430,14 @@ class SubmenuStatusTableTests(unittest.TestCase):
return_value="/usr/bin/ffmpeg"):
result = hub._Hub(None).screen_configure()
self.assertIs(result, tui.Wizard.BACK)
- # Install (faster uninstalled), then Uninstall (qwen installed);
- # no audio.cpp means no model actions. qwen has no Configure entry
- # (its wizard asks nothing to configure).
+ # Start/Stop (qwen installed), then Install (faster uninstalled),
+ # then Uninstall; no audio.cpp means no model actions. qwen has no
+ # Configure entry (its wizard asks nothing to configure).
self.assertEqual([label for label, _ in captured["options"]],
- ["Install Backend", "Uninstall Backend"])
- # ...the shared status table carries the states instead.
- self.assertEqual(captured["table_title"], "Backend Status")
+ ["Start/Stop Backend Servers", "Install Backend",
+ "Uninstall Backend"])
+ # ...the shared status table carries the states instead (no title).
+ self.assertIsNone(captured.get("table_title"))
self.assertEqual(
captured["table_rows"],
[("qwen-tts", "installed", "ok", "body"),
@@ -480,10 +481,11 @@ class SubmenuStatusTableTests(unittest.TestCase):
self.assertIs(result, tui.Wizard.BACK)
# Update sits between Install and Uninstall; it is offered once for
# the whole set of installed backends (faster has nothing on disk
- # and so contributes nothing).
+ # and so contributes nothing). Start/Stop heads the tail actions
+ # (qwen is installed).
self.assertEqual([label for label, _ in captured["options"]],
- ["Install Backend", "Update Backends",
- "Uninstall Backend"])
+ ["Start/Stop Backend Servers", "Install Backend",
+ "Update Backends", "Uninstall Backend"])
def test_configure_backends_menu_audiocpp_model_actions(self):
captured = {}
@@ -513,13 +515,13 @@ class SubmenuStatusTableTests(unittest.TestCase):
labels = self._labels(captured["options"])
# The missing-model download heads the menu as the recommended next
# step (yellow suffix), separated from the rest by a blank line;
- # Configure + Uninstall follow. The backend is built, so no "Build"
- # action is offered. Deleting unused models now lives inside the
- # "Configure audio.cpp" wizard, not here.
+ # Start/Stop + Configure + Uninstall follow. The backend is built,
+ # so no "Build" action is offered. Deleting unused models now lives
+ # inside the "Configure audio.cpp" wizard, not here.
self.assertEqual(
labels,
["Download Missing Models (audio.cpp)", "Configure audio.cpp",
- "Uninstall Backend"])
+ "Start/Stop Backend Servers", "Uninstall Backend"])
self.assertEqual(captured["options"][0],
("Download Missing Models (audio.cpp)",
"download_models", ("[recommended]", "warn")))
@@ -587,7 +589,8 @@ class SubmenuStatusTableTests(unittest.TestCase):
captured = {}
infos = [BackendInfo("audiocpp", "audio.cpp", lambda: None, lambda: 0)]
# Built but no server.json: only Configure (the next step) plus
- # Uninstall — no Build, no Download, no Install entry.
+ # Start/Stop and Uninstall — no Build, no Download, no Install
+ # entry.
statuses = [BackendStatus("audiocpp", "audio.cpp", installed=True,
configured=False)]
with tempfile.TemporaryDirectory() as td:
@@ -605,7 +608,8 @@ class SubmenuStatusTableTests(unittest.TestCase):
result = hub._Hub(None).screen_configure()
self.assertIs(result, tui.Wizard.BACK)
self.assertEqual(self._labels(captured["options"]),
- ["Configure audio.cpp", "Uninstall Backend"])
+ ["Configure audio.cpp", "Start/Stop Backend Servers",
+ "Uninstall Backend"])
def test_configure_menu_offers_qwens_per_model_manager(self):
# qwen ships a dedicated configure screen (per-model weight
@@ -626,7 +630,8 @@ class SubmenuStatusTableTests(unittest.TestCase):
result = hub._Hub(None).screen_configure()
self.assertIs(result, tui.Wizard.BACK)
self.assertEqual(self._labels(captured["options"]),
- ["Configure qwen-tts", "Uninstall Backend"])
+ ["Configure qwen-tts", "Start/Stop Backend Servers",
+ "Uninstall Backend"])
self.assertEqual(captured["table_rows"],
[("qwen-tts", "installed", "ok", "body")])
@@ -682,7 +687,7 @@ class SubmenuStatusTableTests(unittest.TestCase):
result = hub._Hub(None).screen_configure()
self.assertIs(result, tui.Wizard.BACK)
self.assertEqual(self._labels(captured["options"]),
- ["Uninstall Backend"])
+ ["Start/Stop Backend Servers", "Uninstall Backend"])
def test_convert_menu_builds_one_form_with_backend_field(self):
captured = {}
@@ -738,7 +743,7 @@ class SubmenuStatusTableTests(unittest.TestCase):
patch.object(hub.shutil, "which", return_value="/x"):
result = hub._Hub(None).screen_configure()
self.assertIs(result, tui.Wizard.BACK)
- self.assertEqual(captured["table_title"], "Backend Status")
+ self.assertIsNone(captured.get("table_title"))
self.assertEqual(
captured["table_rows"], [("qwen-tts", "installed", "ok",
"body")])
@@ -767,7 +772,7 @@ class SubmenuStatusTableTests(unittest.TestCase):
["audio.cpp"])
# The running/stopped state lives in the status table above the
# menu (not on the entries, whose colors the selection bar covers).
- self.assertEqual(captured["table_title"], "Server Status")
+ self.assertIsNone(captured.get("table_title"))
self.assertEqual(captured["table_rows"],
[("audio.cpp", "stopped", "err", "body")])
@@ -3243,7 +3248,8 @@ class HubNavigationTests(unittest.TestCase):
configured=True, servers=specs)
registry = [self._info("qwen", "qwen-tts")]
titles = []
- script = ["server", specs[0], tui.Wizard.BACK, tui.Wizard.BACK]
+ script = ["configure_backends", "server", specs[0],
+ tui.Wizard.BACK, tui.Wizard.BACK, tui.Wizard.BACK]
def menu(stdscr, title, options, **kwargs):
titles.append(title)
@@ -3260,11 +3266,13 @@ class HubNavigationTests(unittest.TestCase):
return_value=None):
hub._Hub(None).run()
# Selecting a server toggles it directly (no action sub-menu), then
- # Esc steps back one screen at a time to the server list and main.
+ # Esc steps back one screen at a time: server list → Configure
+ # Backends → main menu.
self.assertEqual(
titles,
- ["tts-audiobook-generator", "Start / Stop A Server",
- "Start / Stop A Server", "tts-audiobook-generator"])
+ ["tts-audiobook-generator", "Configure Backends",
+ "Start / Stop A Server", "Start / Stop A Server",
+ "Configure Backends", "tts-audiobook-generator"])
def test_esc_on_main_menu_quits(self):
titles = self._drive([tui.Wizard.BACK], [], [])
diff --git a/app/ui/hub.py b/app/ui/hub.py
index e99a2e1..68417f7 100644
--- a/app/ui/hub.py
+++ b/app/ui/hub.py
@@ -120,19 +120,16 @@ class _Hub:
statuses = detect_all()
options = [("Configure Backends", "configure_backends")]
# Converting works against an external (remote) server too, but
- # configuring one and starting/stopping its servers need it on
- # this machine.
+ # configuring one needs it on this machine.
if any(st.installed or st.running for st in statuses):
options.insert(0, ("Generate Audiobooks", "convert"))
- if any(st.installed for st in statuses):
- options.append(("Start/Stop Backend Servers", "server"))
options.append(("Settings", "settings"))
options.append(("Help", "help"))
options.append(("Quit", "quit"))
choice = tui.menu(
self.stdscr, "tts-audiobook-generator", options,
back_value=tui.Wizard.BACK,
- table_title="Backend Status", table_rows=_status_rows(statuses),
+ table_rows=_status_rows(statuses),
notice_lines=_notice_lines())
if choice is tui.Wizard.BACK or choice == "quit":
return None
@@ -140,8 +137,6 @@ class _Hub:
return self.screen_convert
if choice == "configure_backends":
return self.screen_configure
- if choice == "server":
- return self.screen_server
if choice == "help":
return self.screen_help
return self.screen_settings
@@ -158,9 +153,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), update (every installed
- backend refreshed to the latest upstream version in one task-view
- run), and uninstall.
+ 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.
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
@@ -206,6 +201,8 @@ class _Hub:
options += [(f"Configure {info.label}", ("configure", info.key))
for info in installed if _configurable(info)]
+ if any(st.installed for st in statuses):
+ options.append(("Start/Stop Backend Servers", "server"))
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):
@@ -218,13 +215,14 @@ class _Hub:
back_value=tui.Wizard.BACK,
help_lines=["Install, update, configure, or remove a TTS "
"backend."],
- table_title="Backend Status",
table_rows=_status_rows(statuses),
notice_lines=_notice_lines())
if choice is tui.Wizard.BACK:
return tui.Wizard.BACK
if choice == "install":
return self.screen_install
+ if choice == "server":
+ return self.screen_server
if choice == "update":
_update_backends_action(self.stdscr)
invalidate_detect_cache()
@@ -361,7 +359,6 @@ class _Hub:
title = "Uninstall Backend" if installed_only else "Install Backend"
key = tui.menu(self.stdscr, title, options,
back_value=tui.Wizard.BACK,
- table_title="Backend Status",
table_rows=_status_rows(statuses),
notice_lines=_notice_lines())
if key is tui.Wizard.BACK:
@@ -527,8 +524,9 @@ class _Hub:
return tui.Wizard.BACK
spec = tui.menu(self.stdscr, "Start / Stop A Server", options,
back_value=tui.Wizard.BACK,
- help_lines=["Start/stop local servers manually."],
- table_title="Server Status",
+ help_lines=["Start/stop local servers manually.",
+ "'Generate Audiobooks' handles this "
+ "automatically."],
table_rows=rows,
notice_lines=_notice_lines())
if spec is tui.Wizard.BACK: