将 4130 行的单文件拆分为 13 个职责清晰的模块: - constants.py: 常量 + 可选依赖导入 - sandbox.py: 安全代码沙箱 (AST检查器 + 子进程隔离) - config.py: 配置管理 + 日志 - plugin_manager.py: LiuginManager 插件管理器 - cli_base.py: BaseAICLI 基类 (初始化/引擎/插件/命令) - cli_clawli.py: ClawliMixin 远程模式 - cli_tools.py: ToolMixin 工具调用 - cli_code_exec.py: CodeExecMixin 代码沙箱执行 - cli_display.py: DisplayMixin 显示/输出/图片/帮助 - cli_history.py: HistoryMixin 聊天记录 - cli_core.py: AICLI 主类 + 对话循环 + main() - tui.py: Textual TUI 界面 - __init__.py: 包导出汇总 ai_cli.py 保留为入口文件 (11行), 根目录整洁。 原逻辑完全不变, 仅做结构拆分。
321 lines
14 KiB
Python
321 lines
14 KiB
Python
import threading
|
|
import time
|
|
from colorama import Fore, Style
|
|
|
|
from .constants import CLAWLI_SERVER_AVAILABLE, WEBSOCKET_AVAILABLE
|
|
|
|
try:
|
|
import clawli_server
|
|
except ImportError:
|
|
clawli_server = None
|
|
|
|
try:
|
|
import websocket_server
|
|
except ImportError:
|
|
websocket_server = None
|
|
|
|
|
|
class ClawliMixin:
|
|
"""Clawli 远程模式相关方法"""
|
|
|
|
def _start_clawli_monitor(self):
|
|
"""启动 Clawli 消息监控线程"""
|
|
if self._clawli_monitor_running:
|
|
return
|
|
|
|
self._clawli_monitor_running = True
|
|
print(f"{Fore.CYAN}[Clawli] 消息监控线程已启动{Style.RESET_ALL}")
|
|
|
|
def monitor_loop():
|
|
while self._clawli_monitor_running and self.is_clawli_mode:
|
|
try:
|
|
if CLAWLI_SERVER_AVAILABLE and clawli_server:
|
|
messages = clawli_server.get_messages()
|
|
if messages:
|
|
print(f"[Clawli Monitor] 收到 {len(messages)} 条消息")
|
|
for msg in messages:
|
|
self._handle_clawli_message(msg)
|
|
time.sleep(0.1) # 100ms 检查间隔
|
|
except Exception as e:
|
|
print(f"[Clawli Monitor] 错误: {e}")
|
|
|
|
self._clawli_monitor_thread = threading.Thread(target=monitor_loop, daemon=True)
|
|
self._clawli_monitor_thread.start()
|
|
|
|
def _stop_clawli_monitor(self):
|
|
"""停止 Clawli 消息监控线程"""
|
|
self._clawli_monitor_running = False
|
|
if self._clawli_monitor_thread:
|
|
self._clawli_monitor_thread.join(timeout=1)
|
|
self._clawli_monitor_thread = None
|
|
|
|
def _handle_clawli_message(self, msg: dict):
|
|
"""处理来自手机端的消息"""
|
|
try:
|
|
msg_type = msg.get("type", "")
|
|
|
|
if msg_type == "message":
|
|
content = msg.get("content", "")
|
|
self._process_clawli_user_message(content)
|
|
|
|
elif msg_type == "file":
|
|
file_path = msg.get("path", "")
|
|
text = msg.get("text", "")
|
|
category = msg.get("category", "file")
|
|
|
|
if category == "image":
|
|
self._process_clawli_image(file_path, text)
|
|
else:
|
|
self._process_clawli_user_message(f"@{file_path}\n{text}")
|
|
|
|
except Exception as e:
|
|
print(f"[Clawli] 处理消息错误: {e}")
|
|
|
|
def _process_clawli_user_message(self, content: str):
|
|
"""处理 Clawli 用户消息"""
|
|
if not self.current_engine:
|
|
return
|
|
|
|
full_content = f"{content}\n\n[此消息由 Clawli 手机端发送]"
|
|
|
|
self.shared_conversation_history.append({
|
|
"role": "user",
|
|
"content": full_content
|
|
})
|
|
|
|
system_prompt = self._build_system_prompt(self.get_liugin_usage_prompts())
|
|
response = self.current_engine.generate_response(full_content, system_prompt=system_prompt)
|
|
|
|
self._process_clawli_response(response)
|
|
|
|
def _process_clawli_response(self, response: str):
|
|
"""处理 Clawli AI 响应"""
|
|
text_content, json_data = self._parse_mixed_response(response)
|
|
|
|
if json_data:
|
|
tool_calls = self._extract_tool_calls(json_data)
|
|
|
|
if tool_calls:
|
|
for tool_data in tool_calls:
|
|
tool_name = tool_data.get('tool', '未知工具')
|
|
tool_args = tool_data.get('args', '')
|
|
|
|
if CLAWLI_SERVER_AVAILABLE and clawli_server:
|
|
clawli_server.send_tool_status(tool_name, tool_args, "calling")
|
|
|
|
result = self.process_tool_call(tool_data)
|
|
result_text = result.get('result', '无结果')
|
|
|
|
if CLAWLI_SERVER_AVAILABLE and clawli_server:
|
|
clawli_server.send_tool_status(tool_name, tool_args, "success", result_text)
|
|
|
|
self.shared_conversation_history.append({
|
|
"role": "assistant",
|
|
"content": response
|
|
})
|
|
|
|
tool_result_str = "\n".join([f"工具执行结果: {r}" for r in [result.get('result', '')]])
|
|
self._process_clawli_user_message(f"{tool_result_str}\n请根据工具执行结果继续回答。")
|
|
return
|
|
|
|
final_response = text_content if text_content else response
|
|
|
|
self.shared_conversation_history.append({
|
|
"role": "assistant",
|
|
"content": final_response
|
|
})
|
|
|
|
if CLAWLI_SERVER_AVAILABLE and clawli_server:
|
|
clawli_server.send_message(final_response)
|
|
|
|
def _process_clawli_image(self, image_path: str, text: str):
|
|
"""处理 Clawli 图片消息"""
|
|
try:
|
|
image_engine = getattr(self, 'image_engine', None)
|
|
if not image_engine:
|
|
try:
|
|
from image_engine import get_image_engine
|
|
image_engine = get_image_engine()
|
|
except:
|
|
image_engine = None
|
|
|
|
if image_engine:
|
|
with open(image_path, 'rb') as f:
|
|
image_data = f.read()
|
|
|
|
result = image_engine.analyze(image_data, text or "请描述这张图片")
|
|
self._process_clawli_user_message(f"@{image_path}\n图片内容: {result}\n\n{text}")
|
|
else:
|
|
self._process_clawli_user_message(f"@{image_path}\n{text}")
|
|
|
|
except Exception as e:
|
|
self._process_clawli_user_message(f"@{image_path}\n图片处理失败: {e}\n{text}")
|
|
|
|
def _handle_remote_command(self, args):
|
|
"""处理远程连接命令"""
|
|
if not WEBSOCKET_AVAILABLE:
|
|
print(f"{Fore.RED}错误: websockets 库未安装{Style.RESET_ALL}")
|
|
print(f"{Fore.YELLOW}请运行: pip install websockets{Style.RESET_ALL}")
|
|
return
|
|
|
|
args = args.strip()
|
|
|
|
if not args or args == '':
|
|
print(f"\n{Fore.CYAN}{'='*55}{Style.RESET_ALL}")
|
|
print(f"{Fore.CYAN} 远程连接命令帮助{Style.RESET_ALL}")
|
|
print(f"{Fore.CYAN}{'='*55}{Style.RESET_ALL}")
|
|
print(f"\n{Fore.WHITE}本地模式 (局域网直连):{Style.RESET_ALL}")
|
|
print(f"{Fore.YELLOW} /remote start <端口> <密码>{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE} 端口: 监听端口 (默认9079){Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE} 密码: PC密码 (安卓端需要输入){Style.RESET_ALL}")
|
|
print(f"\n{Fore.WHITE}代理模式 (无公网IP,通过中继服务器):{Style.RESET_ALL}")
|
|
print(f"{Fore.YELLOW} /remote proxy <服务器> <服务器密码> <PC端口> <PC密码>{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE} 服务器: 中继服务器地址:端口{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE} 服务器密码: 连接中继服务器的密码{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE} PC端口: 在服务器上使用的端口{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE} PC密码: 安卓端连接时需要验证{Style.RESET_ALL}")
|
|
print(f"\n{Fore.WHITE}其他命令:{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE} /remote stop - 停止服务{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE} /remote status - 查看状态{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE} /remote ip - 查看本机IP{Style.RESET_ALL}")
|
|
print(f"\n{Fore.CYAN}{'='*55}{Style.RESET_ALL}")
|
|
print(f"\n{Fore.GREEN}示例:{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE} /remote start 9079 mypassword{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE} /remote proxy relay.example.com:9079 serverpwd 12345 pcpwd{Style.RESET_ALL}")
|
|
print(f"{Fore.CYAN}{'='*55}{Style.RESET_ALL}\n")
|
|
return
|
|
|
|
parts = args.split()
|
|
cmd = parts[0].lower()
|
|
|
|
if cmd == 'start':
|
|
port = 9079
|
|
password = ""
|
|
|
|
if len(parts) == 2:
|
|
arg = parts[1]
|
|
if arg.isdigit():
|
|
port = int(arg)
|
|
print(f"{Fore.RED}错误: 请设置PC密码{Style.RESET_ALL}")
|
|
print(f"{Fore.YELLOW}用法: /remote start {port} <密码>{Style.RESET_ALL}")
|
|
return
|
|
else:
|
|
password = arg
|
|
elif len(parts) >= 3:
|
|
try:
|
|
port = int(parts[1])
|
|
password = parts[2]
|
|
except ValueError:
|
|
password = parts[1]
|
|
|
|
if not password:
|
|
print(f"{Fore.RED}错误: 请设置PC密码{Style.RESET_ALL}")
|
|
print(f"{Fore.YELLOW}用法: /remote start <端口> <密码>{Style.RESET_ALL}")
|
|
return
|
|
|
|
if len(password) < 4:
|
|
print(f"{Fore.RED}错误: 密码至少4个字符{Style.RESET_ALL}")
|
|
return
|
|
|
|
if CLAWLI_SERVER_AVAILABLE and clawli_server:
|
|
success = clawli_server.start_server("0.0.0.0", port, password)
|
|
if success:
|
|
self.is_clawli_mode = True
|
|
self._start_clawli_monitor()
|
|
elif websocket_server:
|
|
websocket_server.start_local_server(self, port, password)
|
|
|
|
elif cmd == 'proxy':
|
|
if len(parts) < 5:
|
|
print(f"{Fore.RED}参数不足{Style.RESET_ALL}")
|
|
print(f"{Fore.YELLOW}用法: /remote proxy <服务器> <服务器密码> <PC端口> <PC密码>{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE}示例: /remote proxy relay.example.com:9079 serverpwd 12345 pcpwd{Style.RESET_ALL}")
|
|
return
|
|
|
|
server_addr = parts[1]
|
|
server_password = parts[2]
|
|
pc_port = 0
|
|
pc_password = parts[4] if len(parts) > 4 else ""
|
|
|
|
try:
|
|
pc_port = int(parts[3])
|
|
if pc_port < 10000 or pc_port > 19999:
|
|
print(f"{Fore.YELLOW}建议端口范围: 10000-19999{Style.RESET_ALL}")
|
|
except ValueError:
|
|
print(f"{Fore.RED}无效的端口号: {parts[3]}{Style.RESET_ALL}")
|
|
return
|
|
|
|
if not pc_password:
|
|
print(f"{Fore.RED}错误: 请设置PC密码{Style.RESET_ALL}")
|
|
return
|
|
|
|
if len(pc_password) < 4:
|
|
print(f"{Fore.RED}错误: PC密码至少4个字符{Style.RESET_ALL}")
|
|
return
|
|
|
|
if ':' in server_addr:
|
|
server_host, port_str = server_addr.rsplit(':', 1)
|
|
try:
|
|
server_port = int(port_str)
|
|
except ValueError:
|
|
print(f"{Fore.RED}无效的服务器端口: {port_str}{Style.RESET_ALL}")
|
|
return
|
|
else:
|
|
server_host = server_addr
|
|
server_port = 9079
|
|
|
|
print(f"{Fore.CYAN}正在连接中继服务器...{Style.RESET_ALL}")
|
|
if websocket_server:
|
|
websocket_server.start_proxy_client(
|
|
self, server_host, server_port,
|
|
server_password, pc_port, pc_password
|
|
)
|
|
|
|
elif cmd == 'stop':
|
|
if CLAWLI_SERVER_AVAILABLE and clawli_server:
|
|
clawli_server.stop_server()
|
|
self.is_clawli_mode = False
|
|
elif websocket_server:
|
|
websocket_server.stop_all()
|
|
print(f"{Fore.GREEN}远程服务已停止{Style.RESET_ALL}")
|
|
|
|
elif cmd == 'status':
|
|
if CLAWLI_SERVER_AVAILABLE and clawli_server:
|
|
status = clawli_server.get_status()
|
|
elif websocket_server:
|
|
status = websocket_server.get_status()
|
|
else:
|
|
status = {}
|
|
|
|
if status.get('running'):
|
|
mode = status.get('mode', 'local')
|
|
if mode == 'proxy':
|
|
print(f"{Fore.GREEN}远程服务状态: 运行中 (代理模式){Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE}服务器: {Fore.YELLOW}{status.get('server', '未知')}{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE}PC端口: {Fore.YELLOW}{status.get('port', '未知')}{Style.RESET_ALL}")
|
|
else:
|
|
print(f"{Fore.GREEN}远程服务状态: 运行中 (本地模式){Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE}本机IP: {Fore.YELLOW}{status.get('local_ip', '未知')}{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE}端口: {Fore.YELLOW}{status.get('port', 9079)}{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE}已连接客户端: {Fore.YELLOW}{status.get('connected_clients', 0)}{Style.RESET_ALL}")
|
|
else:
|
|
print(f"{Fore.YELLOW}远程服务状态: 未启动{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE}使用 {Fore.CYAN}/remote start <端口> <密码>{Fore.WHITE} 启动本地服务{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE}使用 {Fore.CYAN}/remote proxy ...{Fore.WHITE} 连接代理服务器{Style.RESET_ALL}")
|
|
|
|
elif cmd == 'ip':
|
|
import socket
|
|
try:
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
s.connect(("8.8.8.8", 80))
|
|
local_ip = s.getsockname()[0]
|
|
s.close()
|
|
print(f"{Fore.GREEN}本机IP地址: {Fore.YELLOW}{local_ip}{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE}在 Clawli 应用中输入此IP即可连接 (仅限局域网){Style.RESET_ALL}")
|
|
except Exception as e:
|
|
print(f"{Fore.RED}获取IP失败: {e}{Style.RESET_ALL}")
|
|
|
|
else:
|
|
print(f"{Fore.RED}未知命令: {cmd}{Style.RESET_ALL}")
|
|
print(f"{Fore.WHITE}可用命令: start, proxy, stop, status, ip{Style.RESET_ALL}")
|