diff options
| author | historia <historiavg@proton.me> | 2026-08-27 17:46:02 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-08-27 17:46:02 -0400 |
| commit | 2a9a78dd1caec0811ed2640b28375890ee96e4cc (patch) | |
| tree | 6aebbb503a98eb035015ff56e51d4ccc4f4a395e /app/ui/hub.py | |
| parent | cef2352a5e81b272d067c2c02eb9588e54edfcfd (diff) | |
| download | tts-audiobook-generator-2a9a78dd1caec0811ed2640b28375890ee96e4cc.tar.gz | |
feat: help menu in tui
Diffstat (limited to 'app/ui/hub.py')
| -rw-r--r-- | app/ui/hub.py | 95 |
1 files changed, 71 insertions, 24 deletions
diff --git a/app/ui/hub.py b/app/ui/hub.py index a57ab6c..fd2d371 100644 --- a/app/ui/hub.py +++ b/app/ui/hub.py @@ -4,7 +4,7 @@ The hub is the single entry point for the whole workflow: it detects which backends are already set up and offers to convert the input directory with one of them, or install/configure/remove a backend via the "Configure -backends" menu. +Backends" menu. The entire hub runs in one curses session, driven by a single ``tui.Wizard`` stack of screens (the ``_Hub`` class below). Every menu/action is a screen @@ -46,8 +46,10 @@ from backends import probe as backend_probe from backends import qwen as qwen_backend from converter import config from converter.converter import ( + AUDIOBOOKS_FOLDER, AUDIO_FORMATS, AudiobookConverter, + BOOKS_FOLDER, LOGS_FOLDER, voice_mode_for, ) @@ -117,20 +119,21 @@ class _Hub: def screen_main(self): statuses = detect_all() - options = [("Configure backends", "configure_backends")] + 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. if any(st.installed or st.running for st in statuses): - options.insert(0, ("Generate audiobooks", "convert")) + 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_title="Backend Status", table_rows=_status_rows(statuses), notice_lines=_notice_lines()) if choice is tui.Wizard.BACK or choice == "quit": return None @@ -140,6 +143,8 @@ class _Hub: return self.screen_configure if choice == "server": return self.screen_server + if choice == "help": + return self.screen_help return self.screen_settings # -- configure / install / uninstall -------------------------------- @@ -191,7 +196,7 @@ class _Hub: options = [] if needs_build: - options.append(("Build audio.cpp server", "build_audiocpp", + options.append(("Build audio.cpp Server", "build_audiocpp", ("[recommended]", "warn"))) elif missing: options.append(("Download Missing Models (audio.cpp)", @@ -205,16 +210,16 @@ class _Hub: 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): - options.append(("Update backends", "update")) + options.append(("Update Backends", "update")) if any(_uninstallable(info, by_key) for info in REGISTRY): options.append(("Uninstall Backend", "uninstall")) choice = tui.menu( - self.stdscr, "Configure backends", options, + self.stdscr, "Configure Backends", options, back_value=tui.Wizard.BACK, help_lines=["Install, update, configure, or remove a TTS " "backend."], - table_title="Backend status", + table_title="Backend Status", table_rows=_status_rows(statuses), notice_lines=_notice_lines()) if choice is tui.Wizard.BACK: @@ -357,7 +362,7 @@ 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_title="Backend Status", table_rows=_status_rows(statuses), notice_lines=_notice_lines()) if key is tui.Wizard.BACK: @@ -377,7 +382,7 @@ class _Hub: return tui.Wizard.BACK fields, builders, statuses = prepared while True: - result = tui.form(self.stdscr, "Generate audiobooks", fields, + result = tui.form(self.stdscr, "Generate Audiobooks", fields, buttons=("Generate!", "Cancel"), start_on_buttons=True, back_value=tui.Wizard.BACK) @@ -474,6 +479,18 @@ class _Hub: tui.flash(self.stdscr, str(exc), "err") return tui.Wizard.BACK + # -- help ------------------------------------------------------------ + + def screen_help(self): + """Show the quick-start Help text in a scrollable dialog. + + A leaf screen: the viewer closes on Esc/q/Enter (its back_value), + so the stack pops back to the menu that opened it. + """ + tui.text_viewer(self.stdscr, "Help", _help_lines(), + back_value=tui.Wizard.BACK) + return tui.Wizard.BACK + # -- servers -------------------------------------------------------- def screen_server(self): @@ -491,7 +508,7 @@ class _Hub: candidates = [st for st in statuses if st.installed] if not candidates: tui.flash(self.stdscr, "No backend is installed yet — use " - "'Configure backends' first.") + "'Configure Backends' first.") return tui.Wizard.BACK options = [] rows = [] @@ -507,12 +524,12 @@ class _Hub: "ok" if running else "err", "body")) if not options: tui.flash(self.stdscr, "No backend server is configured yet — " - "use 'Configure backends' first.") + "use 'Configure Backends' first.") return tui.Wizard.BACK - spec = tui.menu(self.stdscr, "Start / Stop a server", options, + 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", + table_title="Server Status", table_rows=rows, notice_lines=_notice_lines()) if spec is tui.Wizard.BACK: @@ -617,7 +634,7 @@ def _configurable(info) -> bool: model weight installs there) is always configurable; other backends count via their non-trivial setup wizard. Bare qwen — whose wizard asks no questions: ports live in Settings, the speaker is chosen per run on - Generate audiobooks — would only ever flash "already installed", so it + Generate Audiobooks — would only ever flash "already installed", so it stays excluded until it ships a dedicated screen. """ return info.configure_screen is not None or info.key != "qwen" @@ -654,7 +671,7 @@ def _uninstallable(info, by_key: dict) -> bool: def _updatable(info, by_key: dict) -> bool: - """True when the "Update backends" action has something to do for INFO. + """True when the "Update Backends" action has something to do for INFO. The same on-disk predicate as _uninstallable — update acts on exactly what uninstall removes (the pip package / the checkout) — plus the @@ -715,7 +732,7 @@ def _download_models_action(stdscr) -> None: def _update_backends_action(stdscr) -> None: - """Run the "Update backends" action inside the TUI. + """Run the "Update Backends" action inside the TUI. One task-view step per installed backend that implements update, in registry order; each update stops its managed server first (best- @@ -740,17 +757,17 @@ def _update_backends_action(stdscr) -> None: steps = [taskview.TaskStep(f"Update {info.label}", make_work(info)) for info in targets] - rc = taskview.run_steps(stdscr, "Update backends", steps) + rc = taskview.run_steps(stdscr, "Update Backends", steps) if rc == 0: tui.flash(stdscr, "Every backend is up to date (or just " "updated).", "ok") elif rc == 130: - tui.flash(stdscr, "Update cancelled — re-run 'Update backends' " + tui.flash(stdscr, "Update cancelled — re-run 'Update Backends' " "any time.", "warn") else: tui.flash(stdscr, "Some updates did not complete (failed or " "cancelled) — see the log above. Re-run 'Update " - "backends' to retry.", "err") + "Backends' to retry.", "err") def _status_mark(status: Optional[BackendStatus]) -> Tuple[str, str, str]: @@ -814,6 +831,36 @@ def _notice_lines() -> Optional[list]: return None +def _help_lines() -> list: + """The Help screen's quick-start text (folder paths resolved live).""" + return [ + "1. Put your ebooks (epub, txt, or pdf) here:", + str(BOOKS_FOLDER), + "", + "2. Put any .wavs of voices to clone here:", + str(common.VOICES_DIR), + "", + "3. If no backend is installed, go to Configure Backends > " + "Install Backend and install audio.cpp.", + "", + "4. Select TTS models to install. If you're unsure, try these " + "qwen3-tts models:", + "", + "Voice cloning: qwen3_tts_1_7b_base_q8_0", + "Built-in-voice: qwen3_tts_1_7b_customvoice_q8_0", + "", + "It will take a while to build audio.cpp and download the model " + "files.", + "", + "5. Go to Generate Audiobooks. It will automatically start the " + "necessary server, generate the books, and stop it. There is no " + "need to manually start/stop servers.", + "", + "6. Generated audiobooks (m4b, mp3, etc.) will output here:", + str(AUDIOBOOKS_FOLDER), + ] + + def _convert_form(stdscr) -> Optional[tuple]: """Build the Convert-books form (fields + builders), or None to go back. @@ -843,7 +890,7 @@ def _convert_form(stdscr) -> Optional[tuple]: st, True)) if not entries: tui.flash(stdscr, "No backend is ready to convert with yet — use " - "'Configure backends' first.") + "'Configure Backends' first.") return None builders = {} # A backend can appear twice (managed + "[remote]"), so the remote @@ -969,7 +1016,7 @@ def _field_value(fields, key: str, default=None): # Dim hint shown while editing a Language field (Settings and Generate -# audiobooks): which languages a model accepts varies by backend/model. +# Audiobooks): which languages a model accepts varies by backend/model. _LANGUAGE_EDIT_HINT = ["Check model documentation for supported languages."] @@ -1056,7 +1103,7 @@ def _audiocpp_fields(stdscr, api_url: Optional[str] = None, server_json = checkout / "server.json" if checkout else None if not (server_json and server_json.exists()): tui.flash(stdscr, "No audio.cpp server.json found — run " - "'Configure backends' first.") + "'Configure Backends' first.") return None try: data = json.loads(server_json.read_text(encoding="utf-8")) @@ -1178,7 +1225,7 @@ def _audiocpp_fields(stdscr, api_url: Optional[str] = None, """Why a clone-capable entry has no selectable voices.""" if local: return ("No .wav files available to clone — run Configure " - "backends → audio.cpp and add voices to its " + "Backends → audio.cpp and add voices to its " "voice-clone .wav directory.") return ("No .wav files available to clone — the audio.cpp server " f"at {url} hosts none. Configure its voice-clone .wav " |
