From f9f5a19910304e92f254d77e973d47d3cb471e1e Mon Sep 17 00:00:00 2001 From: chengyongru Date: Thu, 18 Jun 2026 17:39:01 +0800 Subject: [PATCH] Route WebUI quick start through channel config --- docs/start-without-technical-background.md | 4 +-- nanobot/cli/onboard.py | 30 ++++++++++++++++++---- tests/agent/test_onboard_logic.py | 17 +++++++++--- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/docs/start-without-technical-background.md b/docs/start-without-technical-background.md index ad9da92c..11bf6bc9 100644 --- a/docs/start-without-technical-background.md +++ b/docs/start-without-technical-background.md @@ -190,14 +190,14 @@ For the first setup, choose `[Q] Quick Start (recommended)`. It asks for the mod If you are following the OpenRouter example: 1. Choose `[Q] Quick Start (recommended)`. -2. Choose `No chat channel yet (recommended)` unless you already have a Telegram, Feishu/Lark, Slack, or Discord bot token ready. +2. Choose `No chat channel yet` unless you already want to configure WebUI, Telegram, Feishu/Lark, Slack, or Discord now. 3. Select OpenRouter. 4. Paste your OpenRouter API key. 5. Enter a model ID, for example `anthropic/claude-sonnet-4.5`. 6. Review the Quick Start summary. 7. Choose `[S] Save and Exit`. -Enable WebUI later from `[C] Chat Channel` -> `WebSocket` after the first local reply works. +If you choose `WebUI / local browser`, the wizard will open the WebSocket settings before it enables WebUI. If OpenRouter says your account cannot use that model, use another OpenRouter model ID that your account can access. diff --git a/nanobot/cli/onboard.py b/nanobot/cli/onboard.py index 84e172e1..2a792e75 100644 --- a/nanobot/cli/onboard.py +++ b/nanobot/cli/onboard.py @@ -54,7 +54,8 @@ _BACK_PRESSED = object() # Sentinel value for back navigation _MODEL_PRESET_CACHE: set[str] = set() _QUICK_START_TARGETS = { - "No chat channel yet (recommended)": None, + "WebUI / local browser (recommended)": "websocket", + "No chat channel yet": None, "Telegram": "telegram", "Feishu / Lark": "feishu", "Slack": "slack", @@ -414,7 +415,7 @@ def _show_main_menu_header() -> None: body = Table.grid(expand=True) body.add_column(ratio=1) body.add_row(f"{__logo__} [bold {_UI_TEXT}]nanobot[/] [{_UI_MUTED}]v{__version__}[/]") - body.add_row(f"[{_UI_ACCENT}]Quick Start configures one AI model first.[/]") + body.add_row(f"[{_UI_ACCENT}]Quick Start configures one AI model and one entry point.[/]") body.add_row( f"[{_UI_MUTED}]WebUI, chat channels, and advanced settings stay available when you need them.[/]" ) @@ -1443,6 +1444,19 @@ def _configure_quick_start_channel(config: Config, channel_name: str | None) -> current = getattr(config.channels, channel_name, None) or {} model = config_cls.model_validate(current) + if channel_name == "websocket": + enable = _input_bool("Enable WebSocket channel for local WebUI", True) + if not enable: + console.print("[yellow]! WebSocket channel was not enabled[/yellow]") + return False + if hasattr(model, "enabled"): + setattr(model, "enabled", True) + updated = _configure_pydantic_model(model, "WebSocket") + if updated is None: + return False + setattr(config.channels, channel_name, updated.model_dump(by_alias=True, exclude_none=True)) + return True + required_fields = _QUICK_START_CHANNEL_FIELDS.get(channel_name, ()) for field_name, prompt in required_fields: value = _input_with_existing(prompt, getattr(model, field_name, ""), "str") @@ -1472,9 +1486,15 @@ def _show_quick_start_summary(config: Config, channel_name: str | None) -> None: ("Provider", preset.provider if preset else "[not set]"), ("Model", preset.model if preset else "[not set]"), ("Entry point", "Not enabled yet" if channel_name is None else channel_name), - ("Next", "Save, then run `nanobot agent -m \"Hello!\"`"), - ("WebUI", "Use [C] Chat Channel -> WebSocket when you are ready"), + ( + "Next", + "Save, then run `nanobot gateway`" + if channel_name + else "Save, then run `nanobot agent -m \"Hello!\"`", + ), ] + if channel_name == "websocket": + rows.append(("WebUI", "Open http://127.0.0.1:8765 after the gateway starts")) _print_summary_panel(rows, "Quick Start") @@ -1489,7 +1509,7 @@ def _configure_quick_start(config: Config) -> None: answer = _select_with_back( "How do you want to use nanobot first?", list(_QUICK_START_TARGETS) + ["<- Back"], - default="No chat channel yet (recommended)", + default="WebUI / local browser (recommended)", ) if answer is _BACK_PRESSED or answer is None or answer == "<- Back": return diff --git a/tests/agent/test_onboard_logic.py b/tests/agent/test_onboard_logic.py index 6cc44466..9a794852 100644 --- a/tests/agent/test_onboard_logic.py +++ b/tests/agent/test_onboard_logic.py @@ -926,18 +926,25 @@ class TestMainMenuUpdate: assert telegram["enabled"] is True assert telegram["token"] == "123:abc" - def test_quick_start_recommended_path_does_not_enable_channel(self, monkeypatch): - """The recommended Quick Start target should leave chat surfaces as explicit opt-ins.""" + def test_quick_start_webui_opens_websocket_config(self, monkeypatch): + """The recommended WebUI path should explicitly route through WebSocket config.""" config = Config() - selections = iter(["No chat channel yet (recommended)", "OpenRouter"]) + selections = iter(["WebUI / local browser (recommended)", "OpenRouter"]) def fake_select_with_back(*_args, **_kwargs): return next(selections) + def fake_configure(model, display_name): + assert display_name == "WebSocket" + assert model.enabled is True + return model + monkeypatch.setattr(onboard_wizard.console, "clear", lambda: None) monkeypatch.setattr(onboard_wizard, "_show_section_header", lambda *a, **kw: None) monkeypatch.setattr(onboard_wizard, "_select_with_back", fake_select_with_back) monkeypatch.setattr(onboard_wizard, "_input_with_existing", lambda *a, **kw: "sk-or-test") + monkeypatch.setattr(onboard_wizard, "_input_bool", lambda *a, **kw: True) + monkeypatch.setattr(onboard_wizard, "_configure_pydantic_model", fake_configure) monkeypatch.setattr( onboard_wizard, "_input_model_with_autocomplete", @@ -948,7 +955,9 @@ class TestMainMenuUpdate: onboard_wizard._configure_quick_start(config) - assert getattr(config.channels, "websocket", None) is None + websocket = getattr(config.channels, "websocket") + assert websocket["enabled"] is True + assert websocket["websocketRequiresToken"] is True assert config.agents.defaults.model_preset == "primary" def test_quick_start_channel_requires_token_before_enable(self, monkeypatch):