fix: 可选依赖改为延迟导入,支持无 tkinter/cv2 环境

- tkinter/cv2/PIL/ascii_magic 改为 try-import,服务器环境可正常运行
- TUI 相关类在 Textual 不可用时使用占位类
- 修复 TUIMainApp 继承问题

本次更新由 mimo 自动化执行,与开发者无关。
This commit is contained in:
Xiaoli Dev committed 2026-04-25 10:55:27 +08:00
1 parent e22652fbbb
commit 95a0e4bc5c
1 file changed
+34 -18
+34 -18
View File
@@ -17,9 +17,21 @@ import threading
from concurrent.futures import ThreadPoolExecutor, as_completed from concurrent.futures import ThreadPoolExecutor, as_completed
from openai import OpenAI from openai import OpenAI
from colorama import init, Fore, Style from colorama import init, Fore, Style
import tkinter as tk # tkinter 仅在视频播放功能中使用,服务器环境可能不可用
from tkinter import ttk try:
import cv2 import tkinter as tk
from tkinter import ttk
TKINTER_AVAILABLE = True
except ImportError:
TKINTER_AVAILABLE = False
# cv2 仅在视频播放功能中使用
try:
import cv2
CV2_AVAILABLE = True
except ImportError:
CV2_AVAILABLE = False
import numpy as np import numpy as np
from http.server import HTTPServer, BaseHTTPRequestHandler from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs from urllib.parse import urlparse, parse_qs
@@ -27,8 +39,18 @@ import socket
import webbrowser import webbrowser
from datetime import datetime from datetime import datetime
import logging import logging
from PIL import Image # 图像显示相关(可选)
from ascii_magic import AsciiArt try:
from PIL import Image
PIL_AVAILABLE = True
except ImportError:
PIL_AVAILABLE = False
try:
from ascii_magic import AsciiArt
ASCII_MAGIC_AVAILABLE = True
except ImportError:
ASCII_MAGIC_AVAILABLE = False
# WebSocket 服务端 # WebSocket 服务端
try: try:
import websocket_server import websocket_server
@@ -68,7 +90,8 @@ except ImportError:
print(f"{Fore.YELLOW}Textual 未安装,TUI 模式不可用。请运行: pip install textual{Style.RESET_ALL}") print(f"{Fore.YELLOW}Textual 未安装,TUI 模式不可用。请运行: pip install textual{Style.RESET_ALL}")
class TUIBridge: if TEXTUAL_AVAILABLE:
class TUIBridge:
"""桥接类:让 TUI 能够调用同步的 AICLI 方法""" """桥接类:让 TUI 能够调用同步的 AICLI 方法"""
def __init__(self, cli): def __init__(self, cli):
@@ -94,7 +117,7 @@ class TUIBridge:
return False return False
class TUIMainApp(App): class TUIMainApp(App):
"""TUI 主应用""" """TUI 主应用"""
CSS = """ CSS = """
@@ -201,23 +224,18 @@ class TUIMainApp(App):
async def _generate_response_async(self, user_input: str) -> None: async def _generate_response_async(self, user_input: str) -> None:
"""异步生成响应 - 通过回调输出到 TUI""" """异步生成响应 - 通过回调输出到 TUI"""
try: try:
# 清除"正在思考"消息
self.call_after_refresh(self._clear_thinking) self.call_after_refresh(self._clear_thinking)
# 包装回调以在主线程执行
def safe_output(message): def safe_output(message):
self.call_after_refresh(self._write_chat, message) self.call_after_refresh(self._write_chat, message)
# 临时设置 CLI 的输出回调
original_callback = self.cli.tui_output_callback original_callback = self.cli.tui_output_callback
self.cli.tui_output_callback = safe_output self.cli.tui_output_callback = safe_output
try: try:
# 在线程池中运行 CLI
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
await loop.run_in_executor(None, self.cli.process_conversation, user_input) await loop.run_in_executor(None, self.cli.process_conversation, user_input)
finally: finally:
# 恢复原始回调
self.cli.tui_output_callback = original_callback self.cli.tui_output_callback = original_callback
self.call_after_refresh(self.update_status) self.call_after_refresh(self.update_status)
@@ -229,19 +247,15 @@ class TUIMainApp(App):
self.is_generating = False self.is_generating = False
def _write_chat(self, message: str) -> None: def _write_chat(self, message: str) -> None:
"""在主线程写入聊天日志"""
try: try:
chat_log = self.query_one("#chat-log", RichLog) chat_log = self.query_one("#chat-log", RichLog)
# RichLog 支持 ANSI 颜色代码,直接写入
chat_log.write(message) chat_log.write(message)
except Exception as e: except Exception as e:
print(f"写入聊天失败: {e}") print(f"写入聊天失败: {e}")
def _clear_thinking(self) -> None: def _clear_thinking(self) -> None:
"""清除'正在思考'消息"""
try: try:
chat_log = self.query_one("#chat-log", RichLog) chat_log = self.query_one("#chat-log", RichLog)
# 移除最后一条"正在思考"消息
children = list(chat_log.children) children = list(chat_log.children)
if children and "正在思考" in str(children[-1]): if children and "正在思考" in str(children[-1]):
chat_log.remove_child(children[-1]) chat_log.remove_child(children[-1])
@@ -252,10 +266,12 @@ class TUIMainApp(App):
import subprocess import subprocess
import sys import sys
self.exit() self.exit()
# 启动新的 CLI 进程
subprocess.Popen([sys.executable, __file__]) subprocess.Popen([sys.executable, __file__])
# 退出当前进程
os._exit(0) os._exit(0)
else:
# Textual 不可用时的占位类
class TUIMainApp:
pass
# 配置文件路径 # 配置文件路径