feat(ui): update UI backend options to use 'cli' and 'tui'

This commit is contained in:
X-iZhang
2026-03-08 15:34:22 +00:00
parent 659fbc7f84
commit 07e57e110b
9 changed files with 40 additions and 40 deletions
+3 -3
View File
@@ -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")):
+5 -5
View File
@@ -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()
+1 -1
View File
@@ -1924,7 +1924,7 @@ def run_textual_interactive(
mode=mode,
model=model,
provider=provider,
ui_backend="textual",
ui_backend="tui",
channels=channels_info,
)
)
+7 -7
View File
@@ -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,
+2 -2
View File
@@ -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(
+1 -1
View File
@@ -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)
+3 -3
View File
@@ -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"]
+4 -4
View File
@@ -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
View File
@@ -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",