fix: 全面修复 TUI 和跨平台兼容性

- constants.py: requests/openai/numpy 改为 try/except 保护
- tui.py: asyncio.get_event_loop() → get_running_loop() (Python 3.10+)
- tui.py: _remove_thinking 增加异常保护
- tui.py: XiaoliTUI 占位类增加 __init__ 和 run 方法
- tui.py: bare except 改为 except Exception
- sandbox.py: resource 模块 try/except 保护 (Windows 兼容)
This commit is contained in:
xiaoli-cli committed 2026-04-25 22:54:41 +08:00
1 parent d819502b00
commit 623d53f06f
2 files changed
+47 -19

No files matched your search

+32 -14
View File
@@ -11,13 +11,43 @@ import sys
import importlib.util import importlib.util
import json import json
import re import re
import requests
import shutil import shutil
import time import time
import threading import threading
from concurrent.futures import ThreadPoolExecutor, as_completed from concurrent.futures import ThreadPoolExecutor, as_completed
from openai import OpenAI
from colorama import init, Fore, Style from colorama import init, Fore, Style
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
import socket
import webbrowser
from datetime import datetime
import logging
import asyncio
from typing import Optional
from io import StringIO
# ── 第三方依赖(全部 try/except 保护) ──
try:
import requests
REQUESTS_AVAILABLE = True
except ImportError:
requests = None
REQUESTS_AVAILABLE = False
try:
from openai import OpenAI
OPENAI_AVAILABLE = True
except ImportError:
OpenAI = None
OPENAI_AVAILABLE = False
try:
import numpy as np
NUMPY_AVAILABLE = True
except ImportError:
np = None
NUMPY_AVAILABLE = False
# tkinter 仅在视频播放功能中使用,服务器环境可能不可用 # tkinter 仅在视频播放功能中使用,服务器环境可能不可用
try: try:
@@ -34,14 +64,6 @@ try:
except ImportError: except ImportError:
CV2_AVAILABLE = False CV2_AVAILABLE = False
import numpy as np
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
import socket
import webbrowser
from datetime import datetime
import logging
# 图像显示相关(可选) # 图像显示相关(可选)
try: try:
from PIL import Image from PIL import Image
@@ -77,10 +99,6 @@ except ImportError:
UNIFIED_TOOL_MANAGER_AVAILABLE = False UNIFIED_TOOL_MANAGER_AVAILABLE = False
# Textual TUI 支持 # Textual TUI 支持
import asyncio
from typing import Optional
from io import StringIO
try: try:
from textual.app import App, ComposeResult from textual.app import App, ComposeResult
from textual.containers import Container, Horizontal, Vertical, VerticalScroll from textual.containers import Container, Horizontal, Vertical, VerticalScroll
+15 -5
View File
@@ -184,7 +184,7 @@ if TEXTUAL_AVAILABLE:
("", "msg-dim"), ("", "msg-dim"),
(" ╔══════════════════════════════════════════════╗", "msg-welcome"), (" ╔══════════════════════════════════════════════╗", "msg-welcome"),
(" ║ ║", "msg-welcome"), (" ║ ║", "msg-welcome"),
(" ║ 🐱 小狸 Pro-CLI v5.0 ║", "msg-welcome"), (" ║ 🐱 小狸 Pro-CLI v5.1 ║", "msg-welcome"),
(" ║ 智能编程助手 · 对标 Claude Code ║", "msg-welcome"), (" ║ 智能编程助手 · 对标 Claude Code ║", "msg-welcome"),
(" ║ ║", "msg-welcome"), (" ║ ║", "msg-welcome"),
(" ╚══════════════════════════════════════════════╝", "msg-welcome"), (" ╚══════════════════════════════════════════════╝", "msg-welcome"),
@@ -257,7 +257,7 @@ if TEXTUAL_AVAILABLE:
line_numbers=True, word_wrap=True) line_numbers=True, word_wrap=True)
self._append(Panel(syntax, border_style=f"dim {_Theme.BORDER}", self._append(Panel(syntax, border_style=f"dim {_Theme.BORDER}",
box=ROUNDED, padding=(0, 1))) box=ROUNDED, padding=(0, 1)))
except: except Exception:
for line in code.split('\n'): for line in code.split('\n'):
self._add(f" {line}", "msg-dim") self._add(f" {line}", "msg-dim")
i += 1 i += 1
@@ -309,7 +309,7 @@ if TEXTUAL_AVAILABLE:
'cls': lambda: self.action_clear(), 'cls': lambda: self.action_clear(),
'model': lambda: self._switch_model(args), 'model': lambda: self._switch_model(args),
'engine': lambda: self._switch_model(args), 'engine': lambda: self._switch_model(args),
'about': lambda: self._system("🐱 小狸 Pro-CLI v5.0 - 智能编程助手"), 'about': lambda: self._system("🐱 小狸 Pro-CLI v5.1 - 智能编程助手"),
'status': lambda: self._show_status(), 'status': lambda: self._show_status(),
'tools': lambda: self._show_tools(), 'tools': lambda: self._show_tools(),
'engines': lambda: self._show_engines(), 'engines': lambda: self._show_engines(),
@@ -398,7 +398,7 @@ if TEXTUAL_AVAILABLE:
self.cli.tui_output_callback = tui_output self.cli.tui_output_callback = tui_output
try: try:
loop = asyncio.get_event_loop() loop = asyncio.get_running_loop()
await loop.run_in_executor( await loop.run_in_executor(
None, self.cli.process_conversation, user_input None, self.cli.process_conversation, user_input
) )
@@ -413,11 +413,16 @@ if TEXTUAL_AVAILABLE:
self.call_after_refresh(self._update_sidebar) self.call_after_refresh(self._update_sidebar)
def _remove_thinking(self): def _remove_thinking(self):
try:
scroll = self.query_one("#chat-scroll") scroll = self.query_one("#chat-scroll")
for child in reversed(list(scroll.children)): for child in reversed(list(scroll.children)):
if '思考中' in str(getattr(child, 'renderable', '')): renderable = getattr(child, 'renderable', None)
text = str(renderable) if renderable is not None else ""
if '思考中' in text:
child.remove() child.remove()
break break
except Exception:
pass
def _write_raw(self, msg): def _write_raw(self, msg):
if '✅' in msg or 'OK 工具' in msg: if '✅' in msg or 'OK 工具' in msg:
@@ -453,4 +458,9 @@ if TEXTUAL_AVAILABLE:
else: else:
# Textual 不可用时的占位类 # Textual 不可用时的占位类
class XiaoliTUI: class XiaoliTUI:
"""Textual 未安装时的占位类"""
def __init__(self, *args, **kwargs):
pass pass
def run(self, *args, **kwargs):
print("TUI 模式不可用:Textual 库未安装")
print("请运行: pip install textual rich")