fix(v8.0): 启用真·全屏 textual TUI 并修复 opacity 崩溃

- 修复崩溃: tui.py MessageBubble.on_mount 用 self.animate('opacity'),但
  textual>=8 的 Widget.opacity 为只读属性会抛 'has no setter',导致每条消息
  气泡崩溃 -> 改 styles.animate 并兜底直接设置。
- run_tui 改为默认真·全屏 textual XiaoliTUI(上方滚动聊天区+底部固定输入框+侧栏),
  rich 面板仅作 textual 不可用时的回退(此前默认的是顺序面板,被指出是'假TUI')。
- 版本统一 v8.0.0: 欢迎框 + /about 改用 VERSION 常量。
- 状态栏联动 token 感知: 显示 used/context_window 进度条(引擎支持时)。
- 新增 /snapshot(/screenshot) 命令: 导出当前屏幕 SVG 到 .tui_snapshots/live.svg,
  让实时 TUI 也能被 AI 自验。
- 新增 tools/tui_real_snapshot.py: textual run_test+Pilot 无头驱动真实 TUI,
  喂对话、断言 DOM(用户/工具成功失败/助手/状态栏/token条/欢迎版本),导出 SVG。
  7 项断言全过。
- 注: launcher 启动会重写 plugins_config.py/requirements.txt(含 tkinter 等错误项),
  提交前已还原,避免误提交漂移。
This commit is contained in:
shuiyu67 committed 2026-08-01 10:30:42 +08:00
1 parent 79957eba0f
commit db9203687d
3 files changed
+191 -15

No files matched your search

+50 -6
View File
@@ -1,6 +1,6 @@
"""TUI 界面模块 - Textual TUI 实现 (动画增强版 + 多行输入)"""
from .constants import TEXTUAL_AVAILABLE
from .constants import TEXTUAL_AVAILABLE, VERSION
if TEXTUAL_AVAILABLE:
import re
@@ -379,7 +379,12 @@ if TEXTUAL_AVAILABLE:
self.styles.opacity = 0
def on_mount(self):
self.animate("opacity", value=1.0, duration=0.3, easing="out_cubic")
# textual >= 8: Widget.opacity 是只读属性,直接 animate 会抛
# "property 'opacity' has no setter"。改为在 styles 上动画,失败则直接到位。
try:
self.styles.animate("opacity", value=1.0, duration=0.3, easing="out_cubic")
except Exception:
self.styles.opacity = 1.0
class TypingIndicator(Static):
@@ -498,7 +503,7 @@ if TEXTUAL_AVAILABLE:
"",
" ╔══════════════════════════════════════════════╗",
" ║ ║",
" ║ 小狸 Pro-CLI v5.4.1 ║",
" ║ 小狸 Pro-CLI v8.0.0 ║",
" ║ 智能编程助手 · 动画增强版 ║",
" ║ ║",
" ╚══════════════════════════════════════════════╝",
@@ -542,7 +547,7 @@ if TEXTUAL_AVAILABLE:
("", "msg-dim"),
(" ╔══════════════════════════════════════════════╗", "msg-welcome"),
(" ║ ║", "msg-welcome"),
(" ║ 小狸 Pro-CLI v5.4.1 ║", "msg-welcome"),
(" ║ 小狸 Pro-CLI v8.0.0 ║", "msg-welcome"),
(" ║ 智能编程助手 · 动画增强版 ║", "msg-welcome"),
(" ║ ║", "msg-welcome"),
(" ╚══════════════════════════════════════════════╝", "msg-welcome"),
@@ -587,7 +592,28 @@ if TEXTUAL_AVAILABLE:
def _update_status(self, text: str):
bar = self.query_one("#status-bar")
engine = self.bridge.current_engine()
bar.update(f" {text} | {engine} | {len(self.bridge.history())} 条对话")
bar.update(f" {text} | {engine} | {len(self.bridge.history())} 条对话{self._token_str()}")
def _token_str(self):
"""token 用量进度(v8.0 token 感知压缩联动,引擎支持时显示)"""
e = getattr(self.cli, 'current_engine', None)
if not e:
return ""
used = getattr(e, 'last_prompt_tokens', None)
win = None
if hasattr(e, 'context_window'):
try:
win = e.context_window()
except Exception:
win = None
if used and win:
ratio = min(1.0, used / win)
filled = int(ratio * 10)
bar_s = "▓" * filled + "░" * (10 - filled)
return f" | {used}/{win} {bar_s}"
if used:
return f" | {used} tok"
return ""
# ── 消息追加 ──
@@ -743,12 +769,14 @@ if TEXTUAL_AVAILABLE:
'cls': lambda: self.action_clear(),
'model': lambda: self._switch_model(args),
'engine': lambda: self._switch_model(args),
'about': lambda: self._system(" 小狸 Pro-CLI v5.4.1 - 智能编程助手"),
'about': lambda: self._system(f" 小狸 Pro-CLI v{VERSION} - 智能编程助手"),
'status': lambda: self._show_status(),
'tools': lambda: self._show_tools(),
'engines': lambda: self._show_engines(),
'tui': lambda: self._system("已在 TUI 模式中"),
'manual': lambda: self._handle_manual(args),
'snapshot': lambda: self._save_screenshot(),
'screenshot': lambda: self._save_screenshot(),
}
handler = cmds.get(name)
@@ -918,6 +946,22 @@ if TEXTUAL_AVAILABLE:
self._system("已取消")
self.is_generating = False
def _save_screenshot(self):
"""导出当前 TUI 屏幕截图(SVG)——AI 可据此自验实时渲染(/snapshot)"""
import os
out_dir = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".tui_snapshots"
)
os.makedirs(out_dir, exist_ok=True)
path = os.path.join(out_dir, "live.svg")
try:
svg = self.export_screenshot()
with open(path, "w", encoding="utf-8") as f:
f.write(svg)
self._system(f"截图已保存: {path}")
except Exception as e:
self._error(f"截图失败: {e}")
else:
# Textual 不可用时的占位类
class XiaoliTUI: