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:
1 parent
d819502b00
commit
623d53f06f
2 files changed
+47
-19
No files matched your search
+32
-14
@@ -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
@@ -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")
|
||||||
Reference in new issue
Block a user