diff options
Diffstat (limited to 'app/backends/qwen.py')
| -rw-r--r-- | app/backends/qwen.py | 128 |
1 files changed, 92 insertions, 36 deletions
diff --git a/app/backends/qwen.py b/app/backends/qwen.py index b170eb8..3416ebe 100644 --- a/app/backends/qwen.py +++ b/app/backends/qwen.py @@ -58,59 +58,98 @@ def _config_port(url: str, fallback: int) -> int: def _wizard(stdscr, args: argparse.Namespace) -> Optional[dict]: - """Linear TUI wizard collecting every qwen-setup decision.""" + """Linear TUI wizard collecting every qwen-setup decision. + + Driven by ``tui.Wizard`` as a stack of screen closures: each screen + shows one widget and returns the next screen, ``Wizard.BACK`` (Esc/q — + pop to the previous screen), or the settings dict. Steps whose value is + already provided by a flag (``--port-custom``, ``--port-clone``, + ``--speaker``, ``--skip-install``) are folded into the ``_after_*`` + guards and never become screens, so Esc always lands on the previous + real screen. Esc on the first screen aborts the wizard. + """ _GO_BACK = object() - - def confirm(question: str, default: bool = True) -> Optional[bool]: - res = tui.confirm(stdscr, question, default=default, - cancel_value=_GO_BACK) - return None if res is _GO_BACK else res - - # Step 0: pip install (if not installed and not skipped). - do_install = False - if not _is_installed() and not args.skip_install: - choice = confirm("qwen-tts is not installed. pip install it now?", - default=True) - if choice is None: - return None - do_install = choice - - # Step 1: ports. - custom_port = args.port_custom - if custom_port is None: + 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 + s["custom_port"] = args.port_custom + return _after_custom_port() + + def screen_custom_port(): port_text = tui.line_edit( stdscr, "CustomVoice (built-in speaker) port", str(_config_port(config.QWEN_API_URL, DEFAULT_CUSTOM_PORT)), validate=lambda s: None if (s.isdigit() and 1 <= int(s) <= 65535) else "Enter a port number between 1 and 65535", - help_lines=["The port for qwen-tts-demo CustomVoice (speaker mode)"]) - custom_port = int(port_text) - clone_port = args.port_clone - if clone_port is None: + help_lines=["The port for qwen-tts-demo CustomVoice (speaker mode)"], + back_value=_GO_BACK) + if port_text is _GO_BACK: + return tui.Wizard.BACK + s["custom_port"] = int(port_text) + return _after_custom_port() + + def _after_custom_port(): + if args.port_clone is None: + return screen_clone_port + s["clone_port"] = args.port_clone + return _after_clone_port() + + def screen_clone_port(): port_text = tui.line_edit( stdscr, "Base (voice clone) port", str(_config_port(config.CLONE_API_URL, DEFAULT_CLONE_PORT)), validate=lambda s: None if (s.isdigit() and 1 <= int(s) <= 65535) else "Enter a port number between 1 and 65535", - help_lines=["The port for qwen-tts-demo Base (voice cloning)"]) - clone_port = int(port_text) - - # Step 2: built-in speaker. - speaker = args.speaker - if speaker is None: + help_lines=["The port for qwen-tts-demo Base (voice cloning)"], + back_value=_GO_BACK) + if port_text is _GO_BACK: + return tui.Wizard.BACK + s["clone_port"] = int(port_text) + return _after_clone_port() + + def _after_clone_port(): + if args.speaker is None: + return screen_speaker + s["speaker"] = args.speaker + return _finalize() + + def screen_speaker(): speaker = tui.menu( stdscr, "Built-in CustomVoice speaker", [(s, s) for s in QWEN_SPEAKERS], default_index=max(0, QWEN_SPEAKERS.index(config.SPEAKER) if config.SPEAKER in QWEN_SPEAKERS else 0), - help_lines=["Used by audiobook.py --backend qwen without --clone"]) + help_lines=["Used by audiobook.py --backend qwen without --clone"], + back_value=_GO_BACK) + if speaker is _GO_BACK: + return tui.Wizard.BACK + s["speaker"] = speaker + return _finalize() + + def _finalize() -> dict: + return { + "do_install": s.get("do_install", False), + "custom_port": s["custom_port"], + "clone_port": s["clone_port"], + "speaker": s["speaker"], + } - return { - "do_install": do_install, - "custom_port": custom_port, - "clone_port": clone_port, - "speaker": speaker, - } + if not _is_installed() and not args.skip_install: + first = screen_install + else: + first = _after_install() + return tui.Wizard().run(first) def _execute(settings: dict) -> int: @@ -160,6 +199,23 @@ def _print_launch_hint(custom_port: int, clone_port: int) -> None: print("Then run: python audiobook.py --backend qwen") +def setup_screen(stdscr) -> int: + """Run the setup wizard on an existing curses screen (the hub's). + + The hub drives this as one screen of its own ``tui.Wizard`` stack, so + Esc on the wizard's first screen simply returns here and the hub pops + back to the menu that launched it. The console tail (pip install / + config sync) runs under ``tui.suspend`` so the hub's curses session + stays intact. Returns 0 on completion, 1 when the user aborted. + """ + args = build_parser().parse_args([]) + settings = _wizard(stdscr, args) + if settings is None: + return 1 + with tui.suspend(stdscr): + return _execute(settings) + + def run_tui(args: Optional[argparse.Namespace] = None) -> int: """Run the qwen setup wizard end-to-end.""" import curses |
