mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 08:13:11 +03:00
fix(cli): foreground the WebUI browser
This commit is contained in:
@@ -23,6 +23,7 @@ from nanobot.cli.webui_support import (
|
|||||||
_gateway_health_bind_note,
|
_gateway_health_bind_note,
|
||||||
_gateway_health_url,
|
_gateway_health_url,
|
||||||
_host_for_local_browser,
|
_host_for_local_browser,
|
||||||
|
_launch_browser,
|
||||||
_prepare_webui_bundle_for_gateway,
|
_prepare_webui_bundle_for_gateway,
|
||||||
_print_foreground_port_conflict,
|
_print_foreground_port_conflict,
|
||||||
_tcp_endpoint_reachable,
|
_tcp_endpoint_reachable,
|
||||||
@@ -864,7 +865,6 @@ def _run_gateway(
|
|||||||
"""Wait for the gateway to bind, then point the user's browser at the webui."""
|
"""Wait for the gateway to bind, then point the user's browser at the webui."""
|
||||||
if not open_browser_url:
|
if not open_browser_url:
|
||||||
return
|
return
|
||||||
import webbrowser
|
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
# Channels start asynchronously. When the caller supplies a backend
|
# Channels start asynchronously. When the caller supplies a backend
|
||||||
@@ -896,8 +896,10 @@ def _run_gateway(
|
|||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
display_url = _webui_display_url(open_browser_url)
|
display_url = _webui_display_url(open_browser_url)
|
||||||
try:
|
try:
|
||||||
webbrowser.open(open_browser_url)
|
if _launch_browser(open_browser_url):
|
||||||
console.print(f"[green]✓[/green] Opened browser at {display_url}")
|
console.print(f"[green]✓[/green] Opened browser at {display_url}")
|
||||||
|
else:
|
||||||
|
console.print(f"[yellow]Could not open browser; visit {display_url}[/yellow]")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
console.print(f"[yellow]Could not open browser ({e}); visit {display_url}[/yellow]")
|
console.print(f"[yellow]Could not open browser ({e}); visit {display_url}[/yellow]")
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
"""Shared WebUI setup, URL, health, and browser helpers."""
|
"""Shared WebUI setup, URL, health, and browser helpers."""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import webbrowser
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
@@ -40,6 +42,7 @@ __all__ = [
|
|||||||
"_gateway_instance_command",
|
"_gateway_instance_command",
|
||||||
"_host_for_local_browser",
|
"_host_for_local_browser",
|
||||||
"_load_webui_setup_config",
|
"_load_webui_setup_config",
|
||||||
|
"_launch_browser",
|
||||||
"_open_webui_browser",
|
"_open_webui_browser",
|
||||||
"_prepare_webui_bundle_for_gateway",
|
"_prepare_webui_bundle_for_gateway",
|
||||||
"_print_foreground_port_conflict",
|
"_print_foreground_port_conflict",
|
||||||
@@ -60,6 +63,20 @@ __all__ = [
|
|||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
def _launch_browser(url: str) -> bool:
|
||||||
|
"""Open *url* and request a foreground browser window."""
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
result = subprocess.run(
|
||||||
|
["open", url],
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
return result.returncode == 0
|
||||||
|
return bool(webbrowser.open(url, new=2, autoraise=True))
|
||||||
|
|
||||||
|
|
||||||
def _confirm_webui_action(message: str, *, yes: bool) -> None:
|
def _confirm_webui_action(message: str, *, yes: bool) -> None:
|
||||||
"""Confirm a WebUI first-run mutation or fail clearly in non-interactive shells."""
|
"""Confirm a WebUI first-run mutation or fail clearly in non-interactive shells."""
|
||||||
if yes:
|
if yes:
|
||||||
@@ -419,14 +436,14 @@ def _print_foreground_port_conflict(
|
|||||||
|
|
||||||
def _open_webui_browser(url: str, *, wait: bool = True) -> None:
|
def _open_webui_browser(url: str, *, wait: bool = True) -> None:
|
||||||
"""Open the WebUI in the user's default browser, with a copyable fallback."""
|
"""Open the WebUI in the user's default browser, with a copyable fallback."""
|
||||||
import webbrowser
|
|
||||||
|
|
||||||
if wait:
|
if wait:
|
||||||
_wait_for_webui(url)
|
_wait_for_webui(url)
|
||||||
display_url = _webui_display_url(url)
|
display_url = _webui_display_url(url)
|
||||||
try:
|
try:
|
||||||
webbrowser.open(url)
|
if _launch_browser(url):
|
||||||
console.print(f"[green]✓[/green] Opened WebUI: [cyan]{display_url}[/cyan]")
|
console.print(f"[green]✓[/green] Opened WebUI: [cyan]{display_url}[/cyan]")
|
||||||
|
else:
|
||||||
|
console.print(f"[yellow]Could not open browser; visit {display_url}[/yellow]")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
console.print(f"[yellow]Could not open browser ({exc}); visit {display_url}[/yellow]")
|
console.print(f"[yellow]Could not open browser ({exc}); visit {display_url}[/yellow]")
|
||||||
|
|
||||||
|
|||||||
@@ -2534,7 +2534,11 @@ def test_webui_yes_still_refuses_invalid_custom_model_setup(
|
|||||||
def test_open_webui_browser_redacts_bootstrap_secret(monkeypatch, capsys) -> None:
|
def test_open_webui_browser_redacts_bootstrap_secret(monkeypatch, capsys) -> None:
|
||||||
opened: list[str] = []
|
opened: list[str] = []
|
||||||
url = "http://127.0.0.1:8765/#/?bootstrapSecret=super-secret"
|
url = "http://127.0.0.1:8765/#/?bootstrapSecret=super-secret"
|
||||||
monkeypatch.setattr("webbrowser.open", lambda value: opened.append(value))
|
monkeypatch.setattr(
|
||||||
|
cli_webui_support,
|
||||||
|
"_launch_browser",
|
||||||
|
lambda value: opened.append(value) or True,
|
||||||
|
)
|
||||||
|
|
||||||
cli_webui_support._open_webui_browser(url, wait=False)
|
cli_webui_support._open_webui_browser(url, wait=False)
|
||||||
|
|
||||||
@@ -2544,6 +2548,42 @@ def test_open_webui_browser_redacts_bootstrap_secret(monkeypatch, capsys) -> Non
|
|||||||
assert "super-secret" not in output
|
assert "super-secret" not in output
|
||||||
|
|
||||||
|
|
||||||
|
def test_open_webui_browser_reports_launch_failure(monkeypatch, capsys) -> None:
|
||||||
|
monkeypatch.setattr(cli_webui_support, "_launch_browser", lambda _value: False)
|
||||||
|
|
||||||
|
cli_webui_support._open_webui_browser("http://127.0.0.1:8765/", wait=False)
|
||||||
|
|
||||||
|
assert "Could not open browser; visit http://127.0.0.1:8765/" in _strip_ansi(
|
||||||
|
capsys.readouterr().out
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_launch_browser_uses_macos_foreground_opener(monkeypatch) -> None:
|
||||||
|
seen: list[list[str]] = []
|
||||||
|
monkeypatch.setattr(cli_webui_support.sys, "platform", "darwin")
|
||||||
|
monkeypatch.setattr(
|
||||||
|
cli_webui_support.subprocess,
|
||||||
|
"run",
|
||||||
|
lambda command, **_kwargs: seen.append(command) or SimpleNamespace(returncode=0),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert cli_webui_support._launch_browser("http://127.0.0.1:8765/") is True
|
||||||
|
assert seen == [["open", "http://127.0.0.1:8765/"]]
|
||||||
|
|
||||||
|
|
||||||
|
def test_launch_browser_uses_default_browser_off_macos(monkeypatch) -> None:
|
||||||
|
opened: list[tuple[str, int, bool]] = []
|
||||||
|
monkeypatch.setattr(cli_webui_support.sys, "platform", "linux")
|
||||||
|
monkeypatch.setattr(
|
||||||
|
cli_webui_support.webbrowser,
|
||||||
|
"open",
|
||||||
|
lambda url, *, new, autoraise: opened.append((url, new, autoraise)) or True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert cli_webui_support._launch_browser("http://127.0.0.1:8765/") is True
|
||||||
|
assert opened == [("http://127.0.0.1:8765/", 2, True)]
|
||||||
|
|
||||||
|
|
||||||
def test_webui_foreground_attaches_to_existing_managed_gateway(monkeypatch, tmp_path: Path) -> None:
|
def test_webui_foreground_attaches_to_existing_managed_gateway(monkeypatch, tmp_path: Path) -> None:
|
||||||
config_file = tmp_path / "config.json"
|
config_file = tmp_path / "config.json"
|
||||||
config_file.write_text("{}")
|
config_file.write_text("{}")
|
||||||
|
|||||||
Reference in New Issue
Block a user