aboutsummaryrefslogtreecommitdiff
path: root/app/ui/hub.py
diff options
context:
space:
mode:
Diffstat (limited to 'app/ui/hub.py')
-rw-r--r--app/ui/hub.py92
1 files changed, 62 insertions, 30 deletions
diff --git a/app/ui/hub.py b/app/ui/hub.py
index c3efce6..5f3d039 100644
--- a/app/ui/hub.py
+++ b/app/ui/hub.py
@@ -79,20 +79,20 @@ def _hub_menu(stdscr) -> Optional[tuple]:
while True:
statuses = detect_all()
options = [("Set up a backend", "setup")]
+ # Converting works against an external (remote) server too, but
+ # configuring one and starting/stopping its servers need it on
+ # this machine.
if any(st.installed or st.running for st in statuses):
options.insert(0, ("Convert books", "convert"))
+ if any(st.installed for st in statuses):
options.append(("Configure a backend", "configure"))
options.append(("Start/Stop Backend Servers", "server"))
options.append(("Settings", "settings"))
options.append(("Quit", "quit"))
- rows = [(st.label, *_status_mark(st)) for st in statuses]
- notice_lines = None
- if shutil.which("ffmpeg") is None:
- notice_lines = [("Warning: ffmpeg not installed!", "err")]
choice = tui.menu(
stdscr, "tts-audiobook-generator", options,
- table_title="Backend status", table_rows=rows,
- notice_lines=notice_lines)
+ table_title="Backend status", table_rows=_status_rows(statuses),
+ notice_lines=_notice_lines())
if choice is None or choice == "quit":
return None
if choice == "convert":
@@ -117,13 +117,14 @@ def _hub_menu(stdscr) -> Optional[tuple]:
def _setup_menu(stdscr, statuses) -> Optional[tuple]:
"""Pick a backend to set up. Returns ("setup", key) or None to go back."""
- by_key = {st.key: st for st in statuses}
- options = [(f"{info.label} ({_status_mark(by_key.get(info.key))[0]})",
- info.key) for info in REGISTRY]
+ options = [(info.label, info.key) for info in REGISTRY]
choice = tui.menu(stdscr, "Set up a backend", options,
back_value=_GO_BACK,
help_lines=["Clone/build/install a backend so you can "
- "convert with it."])
+ "convert with it."],
+ table_title="Backend status",
+ table_rows=_status_rows(statuses),
+ notice_lines=_notice_lines())
if choice is _GO_BACK or choice is None:
return None
return ("setup", choice)
@@ -140,7 +141,11 @@ def _configure_menu(stdscr, statuses) -> Optional[tuple]:
"backend' first.")
return None
options = [(info.label, info.key) for info in installed]
- key = tui.menu(stdscr, "Configure a backend", options, back_value=_GO_BACK)
+ key = tui.menu(stdscr, "Configure a backend", options,
+ back_value=_GO_BACK,
+ table_title="Backend status",
+ table_rows=_status_rows(statuses),
+ notice_lines=_notice_lines())
if key is _GO_BACK or key is None:
return None
info = get(key)
@@ -161,36 +166,57 @@ def _status_mark(status: Optional[BackendStatus]) -> Tuple[str, str, str]:
otherwise 'installed' (orange/warn) when the backend is present on disk,
or 'unavailable' (red/err). A backend that is neither installed nor
running is unusable, so its name is dimmed (NAME_KIND).
+ A running server the hub did not start itself (no live pid file for any
+ of its specs — see ``servers.manages``) is tagged "[remote]"; a
+ multi-model backend (qwen) also names which models answered in
+ parentheses, e.g. "running [remote] (Base, CustomVoice)".
CURSES has no true orange, so the theme's yellow 'warn' is used; it
renders amber/orange on most terminals.
"""
if status is not None and status.running:
- return ("running", "ok", "body")
+ text = "running"
+ if not status.managed:
+ text += " [remote]"
+ if status.running_models:
+ text += " (" + ", ".join(status.running_models) + ")"
+ return (text, "ok", "body")
if status is not None and status.installed:
return ("installed", "warn", "body")
return ("unavailable", "err", "dim")
+def _status_rows(statuses) -> list:
+ """Status-table rows for tui.menu: (label, status, kind, name_kind).
+
+ One row per detected backend, in detect order — the same table the
+ main menu shows, reused on each flow's first picker screen so the
+ backend states stay visible there.
+ """
+ return [(st.label, *_status_mark(st)) for st in statuses]
+
+
+def _notice_lines() -> Optional[list]:
+ """Warning lines shown above the status table, or None when all good."""
+ if shutil.which("ffmpeg") is None:
+ return [("Warning: ffmpeg not installed!", "err")]
+ return None
+
+
def _convert_menu(stdscr, statuses) -> Optional[tuple]:
"""Pick an available backend and collect per-backend run settings."""
available = [st for st in statuses if st.ready or st.running]
- options = [(st.label, st.key) for st in available]
if not available:
- choice = tui.menu(
- stdscr, "No backend is available",
- [("Set up a backend", "__setup__")],
- help_lines=["Set up a backend (clone/build/configure) before "
- "converting."])
- if choice == "__setup__":
- return _setup_menu(stdscr, statuses)
- return None
- options.append(("Set up a backend", "__setup__"))
+ tui.flash(stdscr, "No backend is ready to convert with yet — use "
+ "'Set up a backend' first.")
+ return None
+ options = [(st.label, st.key) for st in available]
+ table = {"table_title": "Backend status",
+ "table_rows": _status_rows(statuses),
+ "notice_lines": _notice_lines()}
key = tui.menu(stdscr, "Convert books with...", options,
- back_value=_GO_BACK)
+ back_value=_GO_BACK, **table)
if key is _GO_BACK or key is None:
return None
- if key == "__setup__":
- return _setup_menu(stdscr, statuses)
if key == BACKEND_AUDIOCPP:
cmd = _convert_audiocpp(stdscr, statuses)
elif key == BACKEND_QWEN:
@@ -406,7 +432,7 @@ def _settings_menu(stdscr) -> None:
"validate": _validate_port,
"note": "Ports apply to servers this tool starts and detecting "
"local servers"},
- {"key": "qwen_clone_port", "label": "qwen-tts Base (clone) port",
+ {"key": "qwen_clone_port", "label": "qwen-tts Base port",
"kind": "text",
"value": str(_port_from_url(config.CLONE_API_URL, 7861)),
"validate": _validate_port},
@@ -661,14 +687,20 @@ def _run_server_action(spec_name: str, action: str) -> None:
def _server_menu(stdscr, statuses) -> Optional[tuple]:
"""Pick a backend, then one of its servers and a Start/Stop action."""
- candidates = [st for st in statuses if st.servers or st.running]
+ # Only backends installed on this machine: a merely-running external
+ # server cannot be stopped from here (stop() refuses without our pid
+ # file), so listing it would dead-end.
+ candidates = [st for st in statuses if st.installed]
if not candidates:
- tui.flash(stdscr, "No backend with a server is available. "
- "Set one up first.")
+ tui.flash(stdscr, "No backend is installed yet — use 'Set up a "
+ "backend' first.")
return None
options = [(st.label, st.key) for st in candidates]
key = tui.menu(stdscr, "Start / Stop a server", options,
- back_value=_GO_BACK)
+ back_value=_GO_BACK,
+ table_title="Backend status",
+ table_rows=_status_rows(statuses),
+ notice_lines=_notice_lines())
if key is _GO_BACK or key is None:
return None
status = next((s for s in statuses if s.key == key), None)