chore: 更新项目配置和 CLI

- 更新 .env.example 环境变量示例
- 更新 pyproject.toml 依赖配置
- 删除 tui 相关命令 (cmd_tui.py, cmd_run_tui.py)
- 更新 CLI i18n 和核心模块
- 删除 tombi.toml
This commit is contained in:
LIghtJUNction
2026-03-27 00:15:49 +08:00
parent eae35ffd64
commit e8cd999596
11 changed files with 21 additions and 398 deletions

View File

@@ -90,12 +90,6 @@ ASTRBOT_DASHBOARD_ENABLE=True
# 默认 Default: zh (跟随系统 locale / follows system locale)
# ASTRBOT_CLI_LANG=zh
# TUI 界面语言
# TUI interface language
# 可选值 Values: zh (中文), en (英文)
# 默认 Default: zh
# ASTRBOT_TUI_LANG=zh
# ------------------------------------------
# 网络配置 / Network Configuration
# ------------------------------------------

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
import sys
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
@@ -9,6 +10,18 @@ __all__ = ["logger"]
def __getattr__(name: str) -> Any:
if name == "cli":
from .cli.__main__ import cli
return cli()
if name == "cli_rs":
from .rust._core import cli
def cli_rs_wrapper() -> None:
return cli(sys.argv)
return cli_rs_wrapper
if name == "logger":
from .core import logger

View File

@@ -7,7 +7,7 @@ import click
from click.shell_completion import get_completion_class
from . import __version__
from .commands import bk, conf, init, plug, run, tui, uninstall
from .commands import bk, conf, init, plug, run, uninstall
from .i18n import t
logo_tmpl = r"""
@@ -85,7 +85,6 @@ cli.add_command(plug)
cli.add_command(conf)
cli.add_command(uninstall)
cli.add_command(bk)
cli.add_command(tui)
@click.command()

View File

@@ -3,7 +3,6 @@ from .cmd_conf import conf
from .cmd_init import init
from .cmd_plug import plug
from .cmd_run import run
from .cmd_tui import tui
from .cmd_uninstall import uninstall
__all__ = ["bk", "conf", "init", "plug", "run", "tui", "uninstall"]
__all__ = ["bk", "conf", "init", "plug", "run", "uninstall"]

View File

@@ -1,307 +0,0 @@
"""AstrBot Run TUI - A beautiful textual interface for running AstrBot.
This module provides a Textual-based TUI for `astrbot run` with:
- Animated ASCII logo
- Live log viewer
- Platform status indicators
- Only activates in interactive TTY environments
"""
from __future__ import annotations
import sys
import typing
from collections.abc import Awaitable, Callable
from pathlib import Path
from typing import Any
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.containers import Container, Horizontal, Vertical
from textual.reactive import reactive
from textual.widgets import Footer, Header, Log, Static
if typing.TYPE_CHECKING:
from rich.console import Console
from rich.style import Style
from rich.text import Text
else:
Console: Any = None
Style: Any = None
Text: Any = None
# AstrBot ASCII Logo
ASTRBOT_LOGO = r"""
___ _______.___________..______ .______ ______ .___________.
/ \ / | || _ \ | _ \ / __ \ | |
/ ^ \ | (----`---| |----`| |_) | | |_) | | | | | `---| |----`
/ /_\ \ \ \ | | | / | _ < | | | | | |
/ _____ \ .----) | | | | |\ \----.| |_) | | `--' | | |
/__/ \__\ |_______/ |__| | _| `._____||______/ \______/ |__|
"""
class AstrBotRunTUI(App):
"""Textual TUI for AstrBot run command."""
CSS = """
Screen {
background: $surface;
}
#logo-container {
height: auto;
padding: 1 2;
background: $surface-darken-1;
border: solid $primary;
}
#logo-text {
color: $primary;
text-style: bold;
font-family: "JetBrains Mono", "Fira Code", monospace;
}
#main-container {
height: 1fr;
}
#log-section {
border: solid $accent;
height: 70%;
margin: 1 2;
}
#log-header {
background: $accent-darken-1;
padding: 1 2;
color: $text;
text-style: bold;
}
Log {
background: $surface-darken-2;
color: $text;
border: solid $accent-darken-2;
}
#status-section {
height: auto;
padding: 1 2;
background: $surface-darken-1;
border-top: solid $primary;
}
.status-item {
padding: 0 2;
}
.status-ok {
color: $success;
text-style: bold;
}
.status-pending {
color: $warning;
}
.status-label {
color: $text-muted;
}
.hidden {
display: none;
}
"""
BINDINGS: typing.ClassVar[list[Binding]] = [
Binding("q", "quit", "Quit", show=True),
Binding("ctrl+c", "quit", "Quit", show=False),
Binding("l", "toggle_logs", "Toggle Logs", show=True),
]
log_visible = reactive(True)
def __init__(
self,
startup_coro: Callable[[], Awaitable[Any]],
astrbot_root: Path,
backend_only: bool = False,
host: str | None = None,
port: str | None = None,
**kwargs: Any,
) -> None:
super().__init__(**kwargs)
self.startup_coro = startup_coro
self.astrbot_root = astrbot_root
self.backend_only = backend_only
self.host = host
self.port = port
self._animation_frame = 0
self._startup_done = False
self._log_lines: list[str] = []
self.console: Any = Console() if Console else None
def compose(self) -> ComposeResult:
"""Create child widgets."""
yield Header()
# Animated Logo
with Container(id="logo-container"):
yield Static(self._get_animated_logo(), id="logo-text")
# Main content
with Vertical(id="main-container"):
# Log viewer
with Container(
id="log-section", classes="" if self.log_visible else "hidden"
):
yield Static("📋 Live Logs", id="log-header")
yield Log(id="log-viewer")
# Status bar
with Horizontal(id="status-section"):
yield Static("🌟 AstrBot", classes="status-item status-ok")
yield Static(
f"📁 {self.astrbot_root.name}",
classes="status-item",
id="root-status",
)
if not self.backend_only:
dashboard_url = (
f"http://{self.host or 'localhost'}:{self.port or '6185'}"
)
yield Static(
f"🌐 Dashboard: [link]{dashboard_url}[/link]",
classes="status-item",
id="dashboard-status",
)
yield Static(
"⚡ Running", classes="status-item status-ok", id="run-status"
)
yield Footer()
def on_mount(self) -> None:
"""Called when app is mounted."""
self.title = "AstrBot"
self.sub_title = "AI Chatbot Framework"
# Start the startup coroutine
self.set_timer(0.1, self._run_startup)
# Animate logo
self.set_interval(0.5, self._animate_logo)
# Get the log widget and configure it
log_widget = self.query_one("#log-viewer", Log)
log_widget.write_line("🚀 AstrBot TUI initialized")
log_widget.write_line(f"📁 Running from: {self.astrbot_root}")
if not self.backend_only:
log_widget.write_line(
f"🌐 Dashboard will be available at: {self.host or 'localhost'}:{self.port or '6185'}"
)
log_widget.write_line("")
def _get_animated_logo(self) -> str:
"""Get the logo with optional animation effect."""
lines = ASTRBOT_LOGO.strip().split("\n")
if self.console and hasattr(self, "_animation_frame"):
# Create animated version with color cycling
frame = self._animation_frame % 4
colors = ["#00D9FF", "#00FF87", "#FFD700", "#FF6B6B"]
color = colors[frame]
text = Text()
for i, line in enumerate(lines):
style = Style(color=color, bold=True) if i == 0 else Style(color=color)
text.append(line + "\n", style=style)
return str(text)
return ASTRBOT_LOGO
def _animate_logo(self) -> None:
"""Update the animated logo."""
self._animation_frame = (self._animation_frame + 1) % 4
logo_widget = self.query_one("#logo-text", Static)
logo_widget.update(self._get_animated_logo())
async def _run_startup(self) -> None:
"""Run the AstrBot startup coroutine."""
if self._startup_done:
return
self._startup_done = True
try:
log_widget = self.query_one("#log-viewer", Log)
log_widget.write_line("⏳ Initializing AstrBot...")
await self.startup_coro()
log_widget.write_line("")
log_widget.write_line("✅ AstrBot started successfully!")
except Exception as e:
log_widget = self.query_one("#log-viewer", Log)
log_widget.write_line(f"❌ Error during startup: {e}")
log_widget.write_line("Check logs for details.")
def action_toggle_logs(self) -> None:
"""Toggle log visibility."""
self.log_visible = not self.log_visible
log_section = self.query_one("#log-section", Container)
if self.log_visible:
log_section.remove_class("hidden")
else:
log_section.add_class("hidden")
async def action_quit(self) -> None:
"""Quit the application."""
self.exit()
def write_log(self, message: str) -> None:
"""Write a message to the log viewer (can be called from outside)."""
log_widget = self.query_one("#log-viewer", Log)
log_widget.write_line(message)
def is_interactive_tty() -> bool:
"""Check if we're running in an interactive TTY."""
return sys.stdin.isatty() and sys.stdout.isatty()
async def run_tui(
startup_coro: Callable[[], Awaitable[Any]],
astrbot_root: Path,
backend_only: bool = False,
host: str | None = None,
port: str | None = None,
) -> None:
"""Run the AstrBot TUI.
Args:
startup_coro: Coroutine to run on startup
astrbot_root: AstrBot root directory
backend_only: Whether backend-only mode is enabled
host: Dashboard host
port: Dashboard port
"""
if not is_interactive_tty():
# Not interactive, run without TUI
await startup_coro()
return
app = AstrBotRunTUI(
startup_coro=startup_coro,
astrbot_root=astrbot_root,
backend_only=backend_only,
host=host,
port=port,
)
try:
await app.run_async()
except Exception:
# Fallback to non-TUI mode
await startup_coro()

View File

@@ -1,68 +0,0 @@
"""TUI CLI command for AstrBot."""
from __future__ import annotations
import sys
import click
@click.command(name="tui")
@click.option(
"--debug",
is_flag=True,
help="Enable debug mode with verbose output.",
)
@click.option(
"--host",
default="http://localhost:6185",
help="AstrBot dashboard host URL.",
)
@click.option(
"--api-key",
default=None,
help="API key for authentication (optional, uses login if not provided).",
)
@click.option(
"--username",
default="astrbot",
help="Username for login (if api-key not provided).",
)
@click.option(
"--password",
default="astrbot",
help="Password for login (if api-key not provided).",
)
def tui(
debug: bool,
host: str,
api_key: str | None,
username: str,
password: str,
) -> None:
"""
Launch the AstrBot Terminal User Interface (TUI).
This command starts an interactive terminal-based interface for AstrBot.
The TUI connects to a running AstrBot instance via the dashboard API.
"""
try:
from astrbot.cli.commands.tui_async import run_tui_async
run_tui_async(
debug=debug,
host=host,
api_key=api_key,
username=username,
password=password,
)
except ImportError as e:
click.echo(f"Error: Failed to import TUI module: {e}", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"Error: Failed to start TUI: {e}", err=True)
if debug:
import traceback
traceback.print_exc()
sys.exit(1)

View File

@@ -91,10 +91,6 @@ _TRANSLATIONS: dict[Language, dict[str, str]] = {
"run_backend_only": "以无界面模式启动",
"run_failed": "启动失败: {error}",
"run_stopped": "AstrBot 已停止",
# TUI command
"tui_starting": "正在启动 TUI...",
"tui_started": "TUI 已启动",
"tui_failed": "TUI 启动失败: {error}",
# Common
"yes": "",
"no": "",
@@ -182,10 +178,6 @@ _TRANSLATIONS: dict[Language, dict[str, str]] = {
"run_backend_only": "Starting in backend-only mode",
"run_failed": "Failed to start: {error}",
"run_stopped": "AstrBot stopped",
# TUI command
"tui_starting": "Starting TUI...",
"tui_started": "TUI started",
"tui_failed": "Failed to start TUI: {error}",
# Common
"yes": "Yes",
"no": "No",

View File

@@ -1268,7 +1268,7 @@ async def build_main_agent(
# Internal tools (source='internal') bypass this check — they are
# not user-togglable in the WebUI, so legacy entries must not block them.
_inactivated: set[str] = set(
sp.get("inactivated_llm_tools", [], scope="global", scope_id="global")
str(sp.get("inactivated_llm_tools", [], scope="global", scope_id="global"))
)
for _tp in config.tool_providers:
_tp_tools = _tp.get_tools(_provider_ctx)

View File

@@ -115,6 +115,9 @@ ignore = [
"E501",
]
[tool.ty]
exclude = ["tests", "tests/**", "**/tests/**"]
[tool.pyright]
typeCheckingMode = "basic"
pythonVersion = "3.10"

View File

@@ -59,8 +59,8 @@ class TestSplitUmo:
def test_non_string_returns_none(self):
"""UMO that is not a string returns None."""
assert UmopConfigRouter._split_umo(None) is None
assert UmopConfigRouter._split_umo(123) is None
assert UmopConfigRouter._split_umo(None) is None # type: ignore 故意这样测试的
assert UmopConfigRouter._split_umo(123) is None # type: ignore 故意这样测试的
def test_four_parts_returns_three(self):
"""UMO with four parts splits to three (last keeps colon)."""

View File

@@ -1,2 +0,0 @@
[schema]
strict = false