feat(ui): update UI backend options to use 'cli' and 'tui'
This commit is contained in:
@@ -418,7 +418,7 @@ def _main_callback(
|
||||
ui: Optional[str] = typer.Option(
|
||||
None,
|
||||
"--ui",
|
||||
help="UI backend: rich (default) or textual (beta).",
|
||||
help="UI backend: tui (default) or cli.",
|
||||
),
|
||||
):
|
||||
"""EvoScientist Agent - AI-powered research & code execution CLI"""
|
||||
@@ -461,8 +461,8 @@ def _main_callback(
|
||||
|
||||
if mode and mode not in ("run", "daemon"):
|
||||
raise typer.BadParameter("--mode must be 'run' or 'daemon'")
|
||||
if ui and ui.lower() not in ("rich", "textual"):
|
||||
raise typer.BadParameter("--ui must be 'rich' or 'textual'")
|
||||
if ui and ui.lower() not in ("cli", "tui"):
|
||||
raise typer.BadParameter("--ui must be 'tui' or 'cli'")
|
||||
|
||||
# --name only makes sense in run mode
|
||||
if name and not (mode == "run" or (not mode and not workdir and not use_cwd and config.default_mode == "run")):
|
||||
|
||||
@@ -161,7 +161,7 @@ def cmd_interactive(
|
||||
provider: str | None = None,
|
||||
run_name: str | None = None,
|
||||
thread_id: str | None = None,
|
||||
ui_backend: str = "rich",
|
||||
ui_backend: str = "cli",
|
||||
config=None,
|
||||
) -> None:
|
||||
"""Interactive conversation mode with streaming output.
|
||||
@@ -179,13 +179,13 @@ def cmd_interactive(
|
||||
provider: LLM provider name to display in banner
|
||||
run_name: Optional run name for /new session deduplication
|
||||
thread_id: Optional thread ID to resume a previous session
|
||||
ui_backend: UI backend ('rich' or 'textual')
|
||||
ui_backend: UI backend ('cli' or 'tui')
|
||||
"""
|
||||
import nest_asyncio
|
||||
nest_asyncio.apply()
|
||||
|
||||
resolved_ui_backend = resolve_ui_backend(ui_backend, warn_fallback=True)
|
||||
if resolved_ui_backend == "textual":
|
||||
if resolved_ui_backend == "tui":
|
||||
from functools import partial
|
||||
load_agent = partial(_load_agent, config=config)
|
||||
run_textual_interactive(
|
||||
@@ -735,7 +735,7 @@ def cmd_run(
|
||||
show_thinking: bool = True,
|
||||
workspace_dir: str | None = None,
|
||||
model: str | None = None,
|
||||
ui_backend: str = "rich",
|
||||
ui_backend: str = "cli",
|
||||
) -> None:
|
||||
"""Single-shot execution with streaming display.
|
||||
|
||||
@@ -746,7 +746,7 @@ def cmd_run(
|
||||
show_thinking: Whether to display thinking panels
|
||||
workspace_dir: Per-session workspace directory path
|
||||
model: Model name for checkpoint metadata
|
||||
ui_backend: UI backend ('rich' or 'textual')
|
||||
ui_backend: UI backend ('cli' or 'tui')
|
||||
"""
|
||||
thread_id = thread_id or generate_thread_id()
|
||||
|
||||
|
||||
@@ -1924,7 +1924,7 @@ def run_textual_interactive(
|
||||
mode=mode,
|
||||
model=model,
|
||||
provider=provider,
|
||||
ui_backend="textual",
|
||||
ui_backend="tui",
|
||||
channels=channels_info,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -7,8 +7,8 @@ from typing import Any, Callable
|
||||
from ..stream.display import console
|
||||
from .tui_backends import RichStreamingBackend, StreamingTUIBackend
|
||||
|
||||
DEFAULT_UI_BACKEND = "rich"
|
||||
SUPPORTED_UI_BACKENDS = ("rich", "textual")
|
||||
DEFAULT_UI_BACKEND = "cli"
|
||||
SUPPORTED_UI_BACKENDS = ("cli", "tui")
|
||||
|
||||
|
||||
def normalize_ui_backend(value: str | None) -> str:
|
||||
@@ -32,11 +32,11 @@ def _has_textual_support() -> bool:
|
||||
def resolve_ui_backend(value: str | None, *, warn_fallback: bool = False) -> str:
|
||||
"""Resolve requested backend and fallback safely when unavailable."""
|
||||
requested = normalize_ui_backend(value)
|
||||
if requested == "textual" and not _has_textual_support():
|
||||
if requested == "tui" and not _has_textual_support():
|
||||
if warn_fallback:
|
||||
console.print(
|
||||
'[yellow]Textual TUI is unavailable (missing textual package). '
|
||||
'Falling back to Rich.[/yellow]'
|
||||
'[yellow]TUI is unavailable (missing textual package). '
|
||||
'Falling back to CLI.[/yellow]'
|
||||
)
|
||||
return DEFAULT_UI_BACKEND
|
||||
return requested
|
||||
@@ -83,9 +83,9 @@ def run_streaming(
|
||||
)
|
||||
except RuntimeError:
|
||||
requested = normalize_ui_backend(ui_backend)
|
||||
if requested == "textual":
|
||||
if requested == "tui":
|
||||
console.print(
|
||||
"[yellow]Textual TUI failed at runtime. Falling back to Rich for this request.[/yellow]"
|
||||
"[yellow]TUI failed at runtime. Falling back to CLI for this request.[/yellow]"
|
||||
)
|
||||
return RichStreamingBackend().run_streaming(
|
||||
agent=agent,
|
||||
|
||||
@@ -478,8 +478,8 @@ def _step_ui_backend(config: EvoScientistConfig) -> str:
|
||||
Selected backend name ("rich" or "textual").
|
||||
"""
|
||||
choices = [
|
||||
Choice(title="Rich CLI (classic terminal)", value="rich"),
|
||||
Choice(title="Textual TUI (full-screen interface)", value="textual"),
|
||||
Choice(title="TUI (full-screen interface, recommended)", value="tui"),
|
||||
Choice(title="CLI (classic terminal, lightweight)", value="cli"),
|
||||
]
|
||||
|
||||
backend = questionary.select(
|
||||
|
||||
@@ -81,7 +81,7 @@ class EvoScientistConfig:
|
||||
|
||||
# UI Settings
|
||||
show_thinking: bool = True
|
||||
ui_backend: Literal["rich", "textual"] = "rich"
|
||||
ui_backend: Literal["cli", "tui"] = "tui"
|
||||
|
||||
# Channel Settings
|
||||
channel_enabled: str = "" # "imessage" | "telegram" | "discord" | "slack" | "wechat" | "dingtalk" | "feishu" | "email" | "qq" | "signal" | "" (comma-separated for multiple)
|
||||
|
||||
@@ -9,7 +9,7 @@ def test_cmd_interactive_dispatches_to_textual(monkeypatch):
|
||||
def _fake_resolve_ui_backend(value, *, warn_fallback=False): # noqa: ANN001
|
||||
captured["resolved_input"] = value
|
||||
captured["warn_fallback"] = warn_fallback
|
||||
return "textual"
|
||||
return "tui"
|
||||
|
||||
def _fake_run_textual_interactive(**kwargs): # noqa: ANN003
|
||||
captured["kwargs"] = kwargs
|
||||
@@ -33,10 +33,10 @@ def test_cmd_interactive_dispatches_to_textual(monkeypatch):
|
||||
provider="demo-provider",
|
||||
run_name="demo-run",
|
||||
thread_id="thread-1",
|
||||
ui_backend="textual",
|
||||
ui_backend="tui",
|
||||
)
|
||||
|
||||
assert captured["resolved_input"] == "textual"
|
||||
assert captured["resolved_input"] == "tui"
|
||||
assert captured["warn_fallback"] is True
|
||||
|
||||
kwargs = captured["kwargs"]
|
||||
|
||||
@@ -76,7 +76,7 @@ class TestEvoScientistConfig:
|
||||
assert config.default_mode == "daemon"
|
||||
assert config.default_workdir == ""
|
||||
assert config.show_thinking is True
|
||||
assert config.ui_backend == "rich"
|
||||
assert config.ui_backend == "tui"
|
||||
assert config.ollama_base_url == ""
|
||||
assert config.imessage_enabled is False
|
||||
assert config.imessage_allowed_senders == ""
|
||||
@@ -315,10 +315,10 @@ class TestPriorityChain:
|
||||
|
||||
def test_env_ui_backend_override(self, temp_config_dir, monkeypatch):
|
||||
"""UI backend can be selected via environment variable."""
|
||||
save_config(EvoScientistConfig(ui_backend="rich"))
|
||||
monkeypatch.setenv("EVOSCIENTIST_UI_BACKEND", "textual")
|
||||
save_config(EvoScientistConfig(ui_backend="cli"))
|
||||
monkeypatch.setenv("EVOSCIENTIST_UI_BACKEND", "tui")
|
||||
config = get_effective_config()
|
||||
assert config.ui_backend == "textual"
|
||||
assert config.ui_backend == "tui"
|
||||
|
||||
def test_env_api_key_override(self, temp_config_dir, monkeypatch):
|
||||
"""Test API keys from env override file."""
|
||||
|
||||
+14
-14
@@ -5,40 +5,40 @@ from dataclasses import dataclass
|
||||
from EvoScientist.cli.tui_runtime import normalize_ui_backend, resolve_ui_backend, run_streaming
|
||||
|
||||
|
||||
def test_normalize_ui_backend_defaults_to_rich():
|
||||
assert normalize_ui_backend(None) == "rich"
|
||||
assert normalize_ui_backend("") == "rich"
|
||||
def test_normalize_ui_backend_defaults_to_cli():
|
||||
assert normalize_ui_backend(None) == "cli"
|
||||
assert normalize_ui_backend("") == "cli"
|
||||
|
||||
|
||||
def test_normalize_ui_backend_accepts_known_values():
|
||||
assert normalize_ui_backend("rich") == "rich"
|
||||
assert normalize_ui_backend("textual") == "textual"
|
||||
assert normalize_ui_backend("TeXtUaL") == "textual"
|
||||
assert normalize_ui_backend("cli") == "cli"
|
||||
assert normalize_ui_backend("tui") == "tui"
|
||||
assert normalize_ui_backend("TUI") == "tui"
|
||||
|
||||
|
||||
def test_normalize_ui_backend_unknown_falls_back_to_rich():
|
||||
assert normalize_ui_backend("unknown-ui") == "rich"
|
||||
def test_normalize_ui_backend_unknown_falls_back_to_cli():
|
||||
assert normalize_ui_backend("unknown-ui") == "cli"
|
||||
|
||||
|
||||
def test_resolve_ui_backend_falls_back_when_textual_unavailable(monkeypatch):
|
||||
monkeypatch.setattr("EvoScientist.cli.tui_runtime._has_textual_support", lambda: False)
|
||||
assert resolve_ui_backend("textual") == "rich"
|
||||
assert resolve_ui_backend("tui") == "cli"
|
||||
|
||||
|
||||
def test_resolve_ui_backend_keeps_textual_when_available(monkeypatch):
|
||||
def test_resolve_ui_backend_keeps_tui_when_available(monkeypatch):
|
||||
monkeypatch.setattr("EvoScientist.cli.tui_runtime._has_textual_support", lambda: True)
|
||||
assert resolve_ui_backend("textual") == "textual"
|
||||
assert resolve_ui_backend("tui") == "tui"
|
||||
|
||||
|
||||
@dataclass
|
||||
class _BrokenBackend:
|
||||
name: str = "textual"
|
||||
name: str = "tui"
|
||||
|
||||
def run_streaming(self, **kwargs): # noqa: ANN003, ANN201
|
||||
raise RuntimeError("boom")
|
||||
|
||||
|
||||
def test_run_streaming_falls_back_to_rich_on_runtime_error(monkeypatch):
|
||||
def test_run_streaming_falls_back_to_cli_on_runtime_error(monkeypatch):
|
||||
monkeypatch.setattr("EvoScientist.cli.tui_runtime.get_backend", lambda *a, **k: _BrokenBackend())
|
||||
|
||||
class _RichStub:
|
||||
@@ -48,7 +48,7 @@ def test_run_streaming_falls_back_to_rich_on_runtime_error(monkeypatch):
|
||||
monkeypatch.setattr("EvoScientist.cli.tui_runtime.RichStreamingBackend", lambda: _RichStub())
|
||||
|
||||
result = run_streaming(
|
||||
ui_backend="textual",
|
||||
ui_backend="tui",
|
||||
agent=object(),
|
||||
message="hello",
|
||||
thread_id="t1",
|
||||
|
||||
Reference in New Issue
Block a user