import tkinter as tk
from tkinter import ttk, messagebox
import subprocess
import threading
import queue
import time
import os
import re
import ctypes
import sys
import json
import socket
from pathlib import Path

CONFIG_DIR = Path.home() / ".xiaobai-openclaw"
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
FAVICON_PATH = Path(__file__).parent / "data" / "favicon.ico"

class ConfigManager:
    """配置管理器 - 负责加载和保存各服务配置"""
    
    # Ollama 默认配置模板
    OLLAMA_TEMPLATE = """# ========================================
# Ollama 服务配置
# ========================================
# 参考: ollama serve -h

# [OLLAMA_HOST]
# 说明: Ollama 服务监听地址和端口
# 默认: 127.0.0.1:11434
# OLLAMA_HOST=127.0.0.1:11434

# [OLLAMA_ORIGINS]
# 说明: 允许的 CORS 跨域来源 (多个用逗号分隔)
# 默认: *
# OLLAMA_ORIGINS=*

# [OLLAMA_MODELS]
# 说明: 模型存储目录路径
# 默认: ~/.ollama/models
# OLLAMA_MODELS=

# [OLLAMA_DEBUG]
# 说明: 启用调试模式 (true/false)
# 默认: false
# OLLAMA_DEBUG=false

# [OLLAMA_KEEP_ALIVE]
# 说明: 模型在内存中保持的时间 (如: 5m, 1h, 24h)
# 默认: 5m
# OLLAMA_KEEP_ALIVE=5m

# [OLLAMA_NUM_PARALLEL]
# 说明: 最大并发请求数
# 默认: 1
# OLLAMA_NUM_PARALLEL=1

# [CUDA_VISIBLE_DEVICES]
# 说明: 指定使用的 GPU 设备 (如: 0, 0,1, 1,2)
# 默认: empty (自动选择)
# CUDA_VISIBLE_DEVICES=
"""

    # Open-WebUI 默认配置模板
    OPENWEBUI_TEMPLATE = """# ========================================
# Open-WebUI 服务配置
# ========================================
# 参考: open-webui serve --help

# [HOST]
# 说明: 服务绑定地址
# 默认: 0.0.0.0
# HOST=0.0.0.0

# [PORT]
# 说明: 服务监听端口
# 默认: 8080
# PORT=8080
"""

    def __init__(self):
        self.ollama_config_file = CONFIG_DIR / "ollama.ini"
        self.webui_config_file = CONFIG_DIR / "open-webui.ini"
        self.claw_config_file = Path.home() / ".openclaw" / "openclaw.json"
        
        # 初始化配置文件
        self._init_config_files()
    
    def _init_config_files(self):
        """初始化配置文件（如果不存在）"""
        # Ollama 配置
        if not self.ollama_config_file.exists():
            self.ollama_config_file.write_text(self.OLLAMA_TEMPLATE)
        
        # Open-WebUI 配置
        if not self.webui_config_file.exists():
            self.webui_config_file.write_text(self.OPENWEBUI_TEMPLATE)
        
        # OpenClaw 配置（不自动创建，让用户安装时再创建）
    
    def load_ollama_config(self):
        """加载 Ollama 配置"""
        config = {}
        try:
            content = self.ollama_config_file.read_text()
            for line in content.split('\n'):
                line = line.strip()
                if line and not line.startswith('#') and '=' in line:
                    key, value = line.split('=', 1)
                    config[key.strip()] = value.strip()
        except Exception as e:
            print(f"加载 Ollama 配置失败: {e}")
        return config
    
    def save_ollama_config(self, config_dict):
        """保存 Ollama 配置"""
        try:
            content = self.ollama_config_file.read_text()
            lines = content.split('\n')
            for i, line in enumerate(lines):
                if line and not line.startswith('#') and '=' in line:
                    key = line.split('=')[0].strip()
                    if key in config_dict:
                        lines[i] = f"{key}={config_dict[key]}"
            self.ollama_config_file.write_text('\n'.join(lines))
            return True
        except Exception as e:
            print(f"保存 Ollama 配置失败: {e}")
            return False
    
    def load_openwebui_config(self):
        """加载 Open-WebUI 配置"""
        config = {}
        try:
            content = self.webui_config_file.read_text()
            for line in content.split('\n'):
                line = line.strip()
                if line and not line.startswith('#') and '=' in line:
                    key, value = line.split('=', 1)
                    config[key.strip()] = value.strip()
        except Exception as e:
            print(f"加载 Open-WebUI 配置失败: {e}")
        return config
    
    def save_openwebui_config(self, config_dict):
        """保存 Open-WebUI 配置"""
        try:
            content = self.webui_config_file.read_text()
            lines = content.split('\n')
            for i, line in enumerate(lines):
                if line and not line.startswith('#') and '=' in line:
                    key = line.split('=')[0].strip()
                    if key in config_dict:
                        lines[i] = f"{key}={config_dict[key]}"
            self.webui_config_file.write_text('\n'.join(lines))
            return True
        except Exception as e:
            print(f"保存 Open-WebUI 配置失败: {e}")
            return False
    
    def load_claw_config(self):
        """加载 OpenClaw 配置"""
        try:
            if self.claw_config_file.exists():
                content = self.claw_config_file.read_text()
                return json.loads(content)
            return {}
        except Exception as e:
            print(f"加载 OpenClaw 配置失败: {e}")
            return {}
    
    def save_claw_config(self, config_dict):
        """保存 OpenClaw 配置"""
        try:
            self.claw_config_file.write_text(json.dumps(config_dict, indent=2, ensure_ascii=False))
            return True
        except Exception as e:
            print(f"保存 OpenClaw 配置失败: {e}")
            return False
    
    def get_claw_url(self):
        """获取 OpenClaw 完整访问 URL
        
        Returns:
            str: 包含 token 的完整 URL，格式为 http://host:port?token=xxx
        """
        config = self.load_claw_config()
        gateway = config.get('gateway', {})
        port = gateway.get('port', 18789)
        auth_token = gateway.get('auth', {}).get('token', '')
        
        url = f"http://localhost:{port}"
        if auth_token:
            url = f"{url}?token={auth_token}"
        return url
    
    def get_ollama_env(self):
        """获取 Ollama 环境变量字典（从配置文件加载，注释变量取默认值）"""
        config = self.load_ollama_config()
        env = {}
        mappings = {
            'OLLAMA_HOST': '127.0.0.1:11434',
            'OLLAMA_ORIGINS': '*',
            'OLLAMA_MODELS': '',
            'OLLAMA_DEBUG': 'false',
            'OLLAMA_KEEP_ALIVE': '5m',
            'OLLAMA_NUM_PARALLEL': '1',
            'CUDA_VISIBLE_DEVICES': ''
        }
        for env_key, default in mappings.items():
            value = config.get(env_key, default)
            if value:
                env[env_key] = value
        return env
    
    def get_openwebui_env(self):
        """获取 Open-WebUI 环境变量字典（从配置文件加载，注释变量取默认值）"""
        config = self.load_openwebui_config()
        env = {}
        mappings = {
            'HOST': '0.0.0.0',
            'PORT': '8080'
        }
        for env_key, default in mappings.items():
            value = config.get(env_key, default)
            if value:
                env[env_key] = value
        return env

class ServiceController:
    def __init__(self, root):
        """初始化服务控制器"""
        self.root = root
        self.running = True  # 窗口运行标志，用于线程安全检查
        # 获取当前用户名
        import getpass
        username = getpass.getuser()
        self.root.title(f"Ollama-OpenWebui—OpenClaw 服务控制器 - {username}")
        self.root.geometry("900x600")
        # self.root.resizable(False, False)
        
        # 设置窗口图标（如果有）
        # self.root.iconbitmap(FAVICON_PATH)
        
        # 配置按钮样式
        self.style = ttk.Style()
        
        # 创建启动按钮样式
        self.style.configure("Start.TButton", 
                           foreground="#000000", 
                           background="#4CAF50",
                           font=('Arial', 10, 'bold'))
        self.style.map("Start.TButton", 
                     foreground=[('pressed', '#000000'), ('active', '#000000')],
                     background=[('pressed', '#45a049'), ('active', '#5dc263')])
        
        # 创建关闭按钮样式
        self.style.configure("Stop.TButton", 
                           foreground="#000000", 
                           background="#f44336",
                           font=('Arial', 10, 'bold'))
        self.style.map("Stop.TButton", 
                     foreground=[('pressed', '#000000'), ('active', '#000000')],
                     background=[('pressed', '#d32f2f'), ('active', '#ff5252')])
        
        # 创建其他按钮样式
        self.style.configure("Other.TButton", 
                           foreground="#000000", 
                           background="#2196F3",
                           font=('Arial', 10, 'bold'))
        self.style.map("Other.TButton", 
                     foreground=[('pressed', '#000000'), ('active', '#000000')],
                     background=[('pressed', '#1976D2'), ('active', '#42a5f5')])
        
        # 子进程管理
        self.processes = {}
        
        # 跟踪正在停止的操作（防止按钮状态竞争）
        self.active_stop_operations = set()
        
        # 日志队列
        self.log_queues = {
            "ollama": queue.Queue(),
            "open-webui": queue.Queue(),
            "openclaw": queue.Queue()
        }
        
        # 日志内容
        self.log_contents = {
            "ollama": [],
            "open-webui": [],
            "openclaw": []
        }
        
        # 日志更新标志
        self.log_updating = False
        
        # 日志文本框字典
        self.log_texts = {}
        self.log_tabs = {}
        
        # Open-WebUI 打开页面选项
        self.open_webui_page = tk.BooleanVar(value=True)
        
        # OpenClaw 打开页面选项
        self.open_claw_page = tk.BooleanVar(value=True)
        
        # 初始化配置管理器
        self.config_manager = ConfigManager()
        
        # 创建主框架
        self.main_frame = ttk.Frame(self.root, padding="10")
        self.main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 创建控制界面
        self.create_control_frame()
        
        # 让日志框架填满剩余空间
        self.create_log_frame()
        self.log_config_notebook.pack(fill=tk.BOTH, expand=True)
        
        # 启动日志更新线程
        self.log_thread = threading.Thread(target=self.update_logs, daemon=True)
        self.log_thread.start()
        
        # 启动服务状态监控线程（实时更新按钮状态）
        self.status_thread = threading.Thread(target=self.monitor_service_status, daemon=True)
        self.status_thread.start()
        
        # 检测服务运行状态
        self.detect_running_services()
        
        # 设置窗口关闭事件
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
    
    def on_closing(self):
        """窗口关闭事件处理"""
        # 创建选择对话框
        dialog = tk.Toplevel(self.root)
        dialog.title("关闭选项")
        dialog.geometry("300x150")
        dialog.transient(self.root)
        dialog.grab_set()
        
        # 居中显示
        dialog.update_idletasks()
        x = (dialog.winfo_screenwidth() // 2) - (dialog.winfo_width() // 2)
        y = (dialog.winfo_screenheight() // 2) - (dialog.winfo_height() // 2)
        dialog.geometry(f"+{x}+{y}")
        
        # 创建选项按钮
        ttk.Label(dialog, text="请选择关闭方式:", font=('Arial', 10)).pack(pady=10)
        
        btn_frame = ttk.Frame(dialog)
        btn_frame.pack(pady=10)
        
        # 隐藏界面按钮
        hide_btn = ttk.Button(btn_frame, text="隐藏界面", command=lambda: self.hide_window(dialog), style="Other.TButton")
        hide_btn.pack(side=tk.LEFT, padx=5)
        
        # 退出程序按钮
        exit_btn = ttk.Button(btn_frame, text="退出程序", command=lambda: self.exit_application(dialog), style="Stop.TButton")
        exit_btn.pack(side=tk.LEFT, padx=5)
    
    def hide_window(self, dialog):
        """隐藏窗口"""
        dialog.destroy()
        
        # 真正隐藏窗口
        self.root.withdraw()
        
        # 显示系统托盘图标
        self.create_tray_icon()
    
    def create_tray_icon(self):
        """创建系统托盘图标"""
        # 创建快捷键来显示窗口（Ctrl+Shift+O）
        self.root.bind_all('<Control-Shift-O>', lambda e: self.show_window())
        try:
            import pystray
            from PIL import Image, ImageDraw
            
            # 创建托盘图标
            image = Image.new('RGB', (64, 64), color=(73, 175, 80))
            d = ImageDraw.Draw(image)
            d.text((10, 20), "OC", fill=(255, 255, 255))
            
            # 创建菜单
            menu = pystray.Menu(
                pystray.MenuItem("显示界面", self.show_window),
                pystray.MenuItem("退出程序", lambda: self.exit_application(None))
            )
            
            # 创建托盘图标
            self.icon = pystray.Icon("OpenClaw", image, "OpenClaw 服务控制器", menu)
            
            # 尝试使用更可靠的方式实现双击功能
            # 注意：pystray库的on_click事件在某些系统上可能不工作
            # 但我们仍然尝试实现它
            try:
                # 添加单击事件
                def on_tray_click(icon, item):
                    # 直接调用show_window方法
                    self.show_window()
                
                # 绑定事件
                self.icon.on_click = on_tray_click
            except Exception as e:
                print(f"无法绑定托盘图标点击事件: {e}")
            
            # 在单独的线程中运行托盘图标
            def run_icon():
                # 运行托盘图标
                self.icon.run()
            
            icon_thread = threading.Thread(target=run_icon, daemon=True)
            icon_thread.start()
            
        except ImportError:
            # 如果没有安装pystray，则提示用户
            # messagebox.showwarning("提示", "系统托盘功能需要安装pystray库\npip install pystray\n\n窗口将隐藏到后台")
            
            # 使用窗口隐藏功能
            self.root.withdraw()
            
            
    
    def show_window(self):
        """显示窗口"""
        self.root.deiconify()
        self.root.lift()
        self.root.focus_force()
        
        # 停止托盘图标
        if hasattr(self, 'icon'):
            try:
                self.icon.stop()
            except:
                pass
    
    def exit_application(self, dialog):
        """退出应用程序"""
        self.running = False  # 设置窗口关闭标志
        
        # 如果dialog不为None，则销毁它
        if dialog:
            dialog.destroy()
        
        # 关闭所有服务
        self.stop_all_services()
        
        # 退出程序
        self.root.quit()
        self.root.destroy()
    
    def stop_all_services(self):
        """停止所有服务"""
        services = ["ollama", "open-webui", "openclaw"]
        for service in services:
            if service in self.processes:
                try:
                    self.stop_service(service)
                except Exception as e:
                    print(f"关闭 {service} 服务时出错: {str(e)}")
    
    def create_control_frame(self):
        """创建控制界面"""
        control_frame = ttk.LabelFrame(self.main_frame, text="服务控制", padding="10")
        control_frame.pack(fill=tk.X, pady=5)
        
        # 创建服务按钮框架
        service_frame = ttk.Frame(control_frame)
        service_frame.pack(fill=tk.X, pady=5)
        
        # Ollama 服务按钮
        ollama_frame = ttk.LabelFrame(service_frame, text="Ollama 服务", padding="5")
        ollama_frame.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        
        self.ollama_start_btn = ttk.Button(ollama_frame, text="启动", command=lambda: self.toggle_service("ollama"), style="Start.TButton")
        self.ollama_start_btn.pack(fill=tk.X, pady=2)
        
        # Open-WebUI 服务按钮
        webui_frame = ttk.LabelFrame(service_frame, text="Open-WebUI 服务", padding="5")
        webui_frame.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        
        # 创建水平框架
        webui_hframe = ttk.Frame(webui_frame)
        webui_hframe.pack(fill=tk.X, pady=2, expand=True)
        
        # 启动按钮
        self.webui_start_btn = ttk.Button(webui_hframe, text="启动", command=lambda: self.toggle_service("open-webui"), style="Start.TButton")
        self.webui_start_btn.pack(side=tk.LEFT, padx=2, pady=2, fill=tk.X, expand=True)
        
        # 打开页面复选项
        webui_check = ttk.Checkbutton(webui_hframe, text="打开页面", variable=self.open_webui_page)
        webui_check.pack(side=tk.LEFT, padx=2, pady=2, fill=tk.X, expand=True)
        
        # OpenClaw 网关按钮
        claw_frame = ttk.LabelFrame(service_frame, text="OpenClaw 网关", padding="5")
        claw_frame.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        
        # 创建水平框架
        claw_hframe = ttk.Frame(claw_frame)
        claw_hframe.pack(fill=tk.X, pady=2, expand=True)
        
        # 启动按钮
        self.claw_start_btn = ttk.Button(claw_hframe, text="启动", command=lambda: self.toggle_service("openclaw"), style="Start.TButton")
        self.claw_start_btn.pack(side=tk.LEFT, padx=2, pady=2, fill=tk.X, expand=True)
        
        # 打开页面复选项
        self.open_claw_page = tk.BooleanVar(value=True)
        claw_check = ttk.Checkbutton(claw_hframe, text="打开页面", variable=self.open_claw_page)
        claw_check.pack(side=tk.LEFT, padx=2, pady=2, fill=tk.X, expand=True)
        
        # 一键检查环境按钮
        check_frame = ttk.LabelFrame(service_frame, text="环境检查", padding="5")
        check_frame.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        
        self.check_env_btn = ttk.Button(check_frame, text="检查", command=self.check_environment, style="Other.TButton")
        self.check_env_btn.pack(fill=tk.X, pady=2)
    
    def check_service_running(self, service):
        """检查服务是否正在运行
        
        Args:
            service: 服务名称 (ollama, open-webui, openclaw)
            
        Returns:
            bool: 服务是否正在运行
        """
        import psutil
        
        try:
            for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
                try:
                    cmdline = proc.info.get('cmdline')
                    if cmdline:
                        cmdline_str = ' '.join(cmdline)
                        cmdline_lower = cmdline_str.lower()
                        
                        if service == 'ollama':
                            # Ollama服务检查: 包含ollama且包含serve
                            if 'ollama' in cmdline_lower and 'serve' in cmdline_lower:
                                return True
                        elif service == 'open-webui':
                            # Open-WebUI服务检查: 包含open-webui或webui serve
                            if 'open-webui' in cmdline_lower:
                                return True
                            # 也检查Python进程是否在运行webui serve
                            if 'webui' in cmdline_lower and 'serve' in cmdline_lower:
                                return True
                        elif service == 'openclaw':
                            # OpenClaw服务检查: 只匹配gateway命令
                            if 'openclaw' in cmdline_lower and 'gateway' in cmdline_lower:
                                return True
                except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess, TypeError, ValueError):
                    pass
        except ImportError:
            # 如果没有安装psutil，则使用系统命令检查
            if os.name == 'nt':  # Windows
                if service == 'ollama':
                    result = subprocess.run(['tasklist', '/fi', 'imagename eq ollama.exe'], capture_output=True, text=True)
                    return 'ollama.exe' in result.stdout
                elif service == 'open-webui':
                    # 对于open-webui，查找运行webui serve的Python进程
                    result = subprocess.run(['tasklist', '/fi', 'imagename eq python.exe', '/v'], capture_output=True, text=True)
                    return 'webui' in result.stdout.lower() or 'open-webui' in result.stdout.lower()
                elif service == 'openclaw':
                    # 对于openclaw，查找gateway进程
                    result = subprocess.run(['tasklist', '/fi', 'imagename eq openclaw.exe', '/v'], capture_output=True, text=True)
                    if 'openclaw.exe' in result.stdout:
                        return True
                    # 也检查python进程
                    result = subprocess.run(['tasklist', '/fi', 'imagename eq python.exe', '/v'], capture_output=True, text=True)
                    return 'gateway' in result.stdout.lower() and 'openclaw' in result.stdout.lower()
            else:  # Linux/macOS
                if service == 'ollama':
                    result = subprocess.run(['pgrep', '-f', 'ollama serve'], capture_output=True, text=True)
                    return result.returncode == 0
                elif service == 'open-webui':
                    result = subprocess.run(['pgrep', '-f', 'open-webui'], capture_output=True, text=True)
                    return result.returncode == 0
                elif service == 'openclaw':
                    result = subprocess.run(['pgrep', '-f', 'openclaw gateway'], capture_output=True, text=True)
                    return result.returncode == 0
        
        return False
    
    def detect_running_services(self):
        """检测所有服务的运行状态"""
        services = ['ollama', 'open-webui', 'openclaw']
        for service in services:
            if self.check_service_running(service):
                # 更新按钮状态
                if service == 'ollama':
                    self.ollama_start_btn.config(text="关闭", style="Stop.TButton")
                elif service == 'open-webui':
                    self.webui_start_btn.config(text="关闭", style="Stop.TButton")
                elif service == 'openclaw':
                    self.claw_start_btn.config(text="关闭", style="Stop.TButton")
                
                # 标记服务正在运行，但不存储进程对象
                # 这样用户点击关闭按钮时会尝试查找并停止进程
                self.processes[service] = "detected"
                
                # 添加启动日志
                log_msg = f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {service} 服务已在运行"
                self.log_queues[service].put(("info", log_msg))
                self.log_contents[service].append(log_msg)
    
    def monitor_service_status(self):
        """实时监控服务状态线程 - 保持按钮与服务状态同步"""
        if not self.running:
            return
        
        services = ['ollama', 'open-webui', 'openclaw']
        
        while self.running:
            for service in services:
                try:
                    is_running = self.check_service_running(service)
                    
                    # 使用 after 在主线程中更新 UI
                    def update_btn(svc, running):
                        if not self.running:
                            return
                        btn_map = {
                            'ollama': self.ollama_start_btn,
                            'open-webui': self.webui_start_btn,
                            'openclaw': self.claw_start_btn
                        }
                        btn = btn_map.get(svc)
                        if btn and btn.winfo_exists():
                            if running:
                                if btn.cget('text') != "关闭":
                                    btn.config(text="关闭", style="Stop.TButton")
                                    if svc not in self.processes:
                                        self.processes[svc] = "detected"
                            else:
                                if btn.cget('text') != "启动":
                                    btn.config(text="启动", style="Start.TButton")
                                    if svc in self.processes:
                                        self.processes.pop(svc, None)
                    
                    self.root.after(0, lambda svc=service, r=is_running: update_btn(svc, r))
                except Exception:
                    pass
            time.sleep(1)
    
    def create_log_frame(self):
        """创建日志/配置管理界面"""
        self.log_config_notebook = ttk.Notebook(self.main_frame)
        self.log_config_notebook.pack(fill=tk.BOTH, pady=5)
        
        # ========== 日志管理选项卡 ==========
        log_frame = ttk.Frame(self.log_config_notebook)
        self.log_config_notebook.add(log_frame, text=" 日志管理 ")
        
        # 日志标签页
        self.notebook = ttk.Notebook(log_frame)
        self.notebook.pack(fill=tk.BOTH, expand=True)
        
        # 创建三个日志标签页
        self.log_tabs = {}
        self.log_texts = {}
        
        for service in ["ollama", "open-webui", "openclaw"]:
            tab = ttk.Frame(self.notebook)
            self.notebook.add(tab, text=service)
            self.log_tabs[service] = tab
            
            # 日志控制栏
            log_control = ttk.Frame(tab)
            log_control.pack(fill=tk.X, pady=5)
            
            # 搜索框
            search_var = tk.StringVar()
            search_entry = ttk.Entry(log_control, textvariable=search_var, width=50)
            search_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
            
            search_btn = ttk.Button(log_control, text="搜索", command=lambda s=service, sv=search_var: self.search_log(s, sv.get()))
            search_btn.pack(side=tk.LEFT, padx=5)
            
            # 清除按钮
            clear_btn = ttk.Button(log_control, text="清除", command=lambda s=service: self.clear_log(s))
            clear_btn.pack(side=tk.RIGHT, padx=5)
            
            # 日志文本框
            text_frame = ttk.Frame(tab)
            text_frame.pack(fill=tk.BOTH, expand=True)
            
            text = tk.Text(text_frame, wrap=tk.WORD, state=tk.DISABLED)
            text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
            
            scrollbar = ttk.Scrollbar(text_frame, orient=tk.VERTICAL, command=text.yview)
            scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
            
            text.config(yscrollcommand=scrollbar.set)
            self.log_texts[service] = text
        
        # ========== 配置管理选项卡 ==========
        config_frame = ttk.Frame(self.log_config_notebook)
        self.log_config_notebook.add(config_frame, text=" 配置管理 ")
        
        # 配置子标签页
        self.config_notebook = ttk.Notebook(config_frame)
        self.config_notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # Ollama 配置
        ollama_config_tab = ttk.Frame(self.config_notebook)
        self.config_notebook.add(ollama_config_tab, text=" Ollama ")
        self.create_ollama_config_ui(ollama_config_tab)
        
        # Open-WebUI 配置
        webui_config_tab = ttk.Frame(self.config_notebook)
        self.config_notebook.add(webui_config_tab, text=" Open-WebUI ")
        self.create_webui_config_ui(webui_config_tab)
        
        # OpenClaw 配置
        claw_config_tab = ttk.Frame(self.config_notebook)
        self.config_notebook.add(claw_config_tab, text=" OpenClaw ")
        self.create_claw_config_ui(claw_config_tab)
    
    def create_ollama_config_ui(self, parent):
        """创建 Ollama 配置界面（可编辑，自动保存）"""
        # 配置文件标签
        config_label = ttk.Label(parent, text=f"文件: {self.config_manager.ollama_config_file}")
        config_label.pack(fill=tk.X, padx=5, pady=(5, 0))
        
        # 提示标签
        tip_label = ttk.Label(parent, text="提示: 修改配置后会自动保存", foreground="red")
        tip_label.pack(fill=tk.X, padx=5, pady=(0, 5))
        
        # 配置文件文本框（可编辑）
        text_frame = ttk.Frame(parent)
        text_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        self.ollama_config_text = tk.Text(text_frame, wrap=tk.WORD)
        self.ollama_config_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        scrollbar2 = ttk.Scrollbar(text_frame, orient=tk.VERTICAL, command=self.ollama_config_text.yview)
        scrollbar2.pack(side=tk.RIGHT, fill=tk.Y)
        self.ollama_config_text.configure(yscrollcommand=scrollbar2.set)
        
        # 绑定自动保存事件
        self.ollama_config_text.bind("<<Modified>>", self.on_ollama_config_changed)
        
        # 加载配置
        self.reload_ollama_config()
    
    def on_ollama_config_changed(self, event):
        """Ollama 配置内容变化时自动保存"""
        if self.ollama_config_text.edit_modified():
            try:
                content = self.ollama_config_text.get(1.0, tk.END)
                self.config_manager.ollama_config_file.write_text(content)
                self.ollama_config_text.edit_modified(False)
            except Exception as e:
                print(f"自动保存 Ollama 配置失败: {e}")
    
    def create_webui_config_ui(self, parent):
        """创建 Open-WebUI 配置界面（可编辑，自动保存）"""
        # 配置文件标签
        config_label = ttk.Label(parent, text=f"文件: {self.config_manager.webui_config_file}")
        config_label.pack(fill=tk.X, padx=5, pady=(5, 0))
        
        # 提示标签
        tip_label = ttk.Label(parent, text="提示: 修改配置后会自动保存", foreground="red")
        tip_label.pack(fill=tk.X, padx=5, pady=(0, 5))
        
        # 配置文件文本框（可编辑）
        text_frame = ttk.Frame(parent)
        text_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        self.webui_config_text = tk.Text(text_frame, wrap=tk.WORD)
        self.webui_config_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        scrollbar2 = ttk.Scrollbar(text_frame, orient=tk.VERTICAL, command=self.webui_config_text.yview)
        scrollbar2.pack(side=tk.RIGHT, fill=tk.Y)
        self.webui_config_text.configure(yscrollcommand=scrollbar2.set)
        
        # 绑定自动保存事件
        self.webui_config_text.bind("<<Modified>>", self.on_webui_config_changed)
        
        self.reload_webui_config()
    
    def on_webui_config_changed(self, event):
        """Open-WebUI 配置内容变化时自动保存"""
        if self.webui_config_text.edit_modified():
            try:
                content = self.webui_config_text.get(1.0, tk.END)
                self.config_manager.webui_config_file.write_text(content)
                self.webui_config_text.edit_modified(False)
            except Exception as e:
                print(f"自动保存 Open-WebUI 配置失败: {e}")
    
    def create_claw_config_ui(self, parent):
        """创建 OpenClaw 配置界面（可编辑，自动保存）"""
        # 配置文件标签
        config_label = ttk.Label(parent, text=f"文件: {self.config_manager.claw_config_file}")
        config_label.pack(fill=tk.X, padx=5, pady=(5, 0))
        
        # 提示标签
        tip_label = ttk.Label(parent, text="提示: 修改配置后会自动保存", foreground="red")
        tip_label.pack(fill=tk.X, padx=5, pady=(0, 5))
        
        # 配置文件文本框（可编辑）
        text_frame = ttk.Frame(parent)
        text_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        self.claw_config_text = tk.Text(text_frame, wrap=tk.WORD)
        self.claw_config_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        scrollbar2 = ttk.Scrollbar(text_frame, orient=tk.VERTICAL, command=self.claw_config_text.yview)
        scrollbar2.pack(side=tk.RIGHT, fill=tk.Y)
        self.claw_config_text.configure(yscrollcommand=scrollbar2.set)
        
        # 绑定自动保存事件
        self.claw_config_text.bind("<<Modified>>", self.on_claw_config_changed)
        
        self.reload_claw_config()
    
    def on_claw_config_changed(self, event):
        """OpenClaw 配置内容变化时自动保存"""
        if self.claw_config_text.edit_modified():
            try:
                content = self.claw_config_text.get(1.0, tk.END)
                config = json.loads(content)
                self.config_manager.save_claw_config(config)
                self.claw_config_text.edit_modified(False)
            except json.JSONDecodeError as e:
                error_msg = f"JSON 格式错误: {e}\n\n请检查配置文件的语法，确保是有效的 JSON 格式。"
                messagebox.showerror("配置保存失败", error_msg)
                self.claw_config_text.edit_modified(False)
            except Exception as e:
                print(f"自动保存 OpenClaw 配置失败: {e}")
    
    def reload_ollama_config(self):
        """重新加载 Ollama 配置"""
        try:
            content = self.config_manager.ollama_config_file.read_text()
            self.ollama_config_text.delete(1.0, tk.END)
            self.ollama_config_text.insert(1.0, content)
        except Exception as e:
            print(f"加载 Ollama 配置失败: {e}")
    
    def reload_webui_config(self):
        """重新加载 Open-WebUI 配置"""
        try:
            content = self.config_manager.webui_config_file.read_text()
            self.webui_config_text.delete(1.0, tk.END)
            self.webui_config_text.insert(1.0, content)
        except Exception as e:
            print(f"加载 Open-WebUI 配置失败: {e}")
    
    def reload_claw_config(self):
        """重新加载 OpenClaw 配置"""
        try:
            content = self.config_manager.claw_config_file.read_text()
            self.claw_config_text.delete(1.0, tk.END)
            self.claw_config_text.insert(1.0, content)
        except Exception as e:
            print(f"加载 OpenClaw 配置失败: {e}")
    
    def toggle_service(self, service):
        """切换服务状态"""
        if service in self.processes:
            # 关闭服务
            self.stop_service(service)
        else:
            # 启动服务
            self.start_service(service)
    
    def wait_for_port_and_open_browser(self, service):
        """等待服务端口开启，然后打开浏览器"""
        import webbrowser
        
        if service == "open-webui":
            host, port = "localhost", 8080
            open_page = self.open_webui_page.get()
            url = "http://localhost:8080"
        elif service == "openclaw":
            host, port = "localhost", 18789
            open_page = self.open_claw_page.get()
            url = self.config_manager.get_claw_url()
        else:
            return
        
        if not open_page:
            return
        
        self.log_queues[service].put(("info", f"正在等待 {host}:{port} 端口开启..."))
        
        # 等待端口开启，最多30秒
        for _ in range(60):
            time.sleep(0.5)
            try:
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.settimeout(1)
                result = sock.connect_ex((host, port))
                sock.close()
                if result == 0:
                    self.log_queues[service].put(("info", f"端口 {port} 已开启，正在打开浏览器..."))
                    webbrowser.open(url)
                    self.log_queues[service].put(("info", f"已打开页面: {url}"))
                    return
            except Exception:
                pass
        
        self.log_queues[service].put(("warning", f"等待端口 {port} 超时，浏览器未打开"))
    
    def start_service(self, service):
        """启动服务"""
        try:
            # 清空日志
            self.log_contents[service] = []
            self.log_queues[service].put(("info", f"=== 启动 {service} 服务 ==="))
            
            # 切换到对应的日志选项卡
            self.notebook.select(self.log_tabs[service])
            
            # 检查服务是否已经在运行
            if self.check_service_running(service):
                self.log_queues[service].put(("warning", f"{service} 服务已经在运行"))
                return
            
            # 设置按钮为正在启动状态
            if service == "ollama":
                self.ollama_start_btn.config(text="正在启动...", state="disabled")
            elif service == "open-webui":
                self.webui_start_btn.config(text="正在启动...", state="disabled")
            elif service == "openclaw":
                self.claw_start_btn.config(text="正在启动...", state="disabled")
            
            # 从配置文件加载环境变量
            if service == "ollama":
                env_vars = self.config_manager.get_ollama_env()
                cmd = "ollama serve"
            elif service == "open-webui":
                env_vars = self.config_manager.get_openwebui_env()
                cmd = "open-webui serve"
            elif service == "openclaw":
                env_vars = {}
                cmd = "openclaw gateway start"
            else:
                return
            
            # 在Windows上使用CREATE_NO_WINDOW标志来隐藏控制台窗口
            creation_flags = 0
            if os.name == 'nt':
                creation_flags = subprocess.CREATE_NO_WINDOW
            
            # 准备环境变量
            process_env = os.environ.copy()
            process_env.update(env_vars)
            
            # 输出配置信息
            if env_vars:
                self.log_queues[service].put(("info", f"配置: {', '.join([f'{k}={v}' for k, v in env_vars.items()])}"))
            
            process = subprocess.Popen(
                cmd, 
                shell=True, 
                stdout=subprocess.PIPE, 
                stderr=subprocess.STDOUT, 
                text=True,
                creationflags=creation_flags,
                env=process_env
            )
            
            self.processes[service] = process
            
            # 启动日志读取线程
            log_thread = threading.Thread(target=self.read_process_output, args=(service, process), daemon=True)
            log_thread.start()
            
            # 延迟检查服务状态，确保服务已经启动
            def check_and_update_status():
                """检查服务状态并更新按钮状态"""
                time.sleep(3)  # 等待服务启动
                
                # 再次检查服务是否真的在运行
                if self.check_service_running(service):
                    # 服务已启动，更新按钮状态
                    if service == "ollama":
                        self.ollama_start_btn.config(text="关闭", state="normal", style="Stop.TButton")
                    elif service == "open-webui":
                        self.webui_start_btn.config(text="关闭", state="normal", style="Stop.TButton")
                    elif service == "openclaw":
                        self.claw_start_btn.config(text="关闭", state="normal", style="Stop.TButton")
                    
                    self.log_queues[service].put(("info", f"{service} 服务已启动"))
                    
                    # 启动独立的浏览器打开线程
                    browser_thread = threading.Thread(
                        target=self.wait_for_port_and_open_browser, 
                        args=(service,),
                        daemon=True
                    )
                    browser_thread.start()
                else:
                    # 服务启动失败，恢复按钮状态
                    self.log_queues[service].put(("warning", f"{service} 服务启动可能失败，请检查日志"))
                    if service == "ollama":
                        self.ollama_start_btn.config(text="启动", state="normal", style="Start.TButton")
                    elif service == "open-webui":
                        self.webui_start_btn.config(text="启动", state="normal", style="Start.TButton")
                    elif service == "openclaw":
                        self.claw_start_btn.config(text="启动", state="normal", style="Start.TButton")
                    
                    # 移除进程记录
                    if service in self.processes:
                        del self.processes[service]
            
            # 在后台线程中检查状态
            status_thread = threading.Thread(target=check_and_update_status, daemon=True)
            status_thread.start()
            
        except FileNotFoundError as e:
            """命令未找到异常"""
            self.log_queues[service].put(("error", f"找不到 '{cmd}' 命令，请确保已安装该软件"))
            # 直接打开环境检查窗口
            self.after(100, self.check_environment)
            # 恢复按钮状态
            if service == "ollama":
                self.ollama_start_btn.config(text="启动", state="normal", style="Start.TButton")
            elif service == "open-webui":
                self.webui_start_btn.config(text="启动", state="normal", style="Start.TButton")
            elif service == "openclaw":
                self.claw_start_btn.config(text="启动", state="normal", style="Start.TButton")
        except Exception as e:
            self.log_queues[service].put(("error", f"启动服务失败: {str(e)}"))
            # 恢复按钮状态
            if service == "ollama":
                self.ollama_start_btn.config(text="启动", state="normal", style="Start.TButton")
            elif service == "open-webui":
                self.webui_start_btn.config(text="启动", state="normal", style="Start.TButton")
            elif service == "openclaw":
                self.claw_start_btn.config(text="启动", state="normal", style="Start.TButton")
    
    def find_service_pids(self, service):
        """查找服务的进程ID列表
        
        Args:
            service: 服务名称
            
        Returns:
            list: 进程ID列表
        """
        import psutil
        pids = []
        
        # 获取当前进程的PID，用于排除
        current_pid = os.getpid()
        
        try:
            for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
                try:
                    # 排除当前进程
                    if proc.info['pid'] == current_pid:
                        continue
                    
                    proc_name = proc.info.get('name', '')
                    cmdline = proc.info.get('cmdline')
                    
                    if service == 'ollama':
                        # Ollama服务检查: 进程名为ollama.exe或ollama app.exe
                        if proc_name in ('ollama.exe', 'ollama app.exe'):
                            pids.append(proc.info['pid'])
                    elif service == 'open-webui':
                        # Open-WebUI服务检查: 包含open-webui或webui serve
                        if cmdline:
                            cmdline_str = ' '.join(cmdline).lower()
                            if 'open-webui' in cmdline_str or ('webui' in cmdline_str and 'serve' in cmdline_str):
                                pids.append(proc.info['pid'])
                    elif service == 'openclaw':
                        # OpenClaw服务检查: 只匹配gateway命令
                        if cmdline:
                            cmdline_str = ' '.join(cmdline).lower()
                            if 'openclaw' in cmdline_str and 'gateway' in cmdline_str:
                                pids.append(proc.info['pid'])
                except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess, TypeError, ValueError):
                    pass
        except ImportError:
            # 如果没有安装psutil，使用系统命令查找
            if os.name == 'nt':  # Windows
                if service == 'ollama':
                    # Ollama进程可能是ollama.exe或ollama app.exe
                    for exe_name in ['ollama.exe', 'ollama app.exe']:
                        result = subprocess.run(['tasklist', '/fi', f'imagename eq {exe_name}', '/fo', 'csv'], capture_output=True, text=True)
                        for line in result.stdout.split('\n'):
                            if exe_name in line:
                                parts = line.split(',')
                                if len(parts) > 1:
                                    try:
                                        pid = int(parts[1].strip('"'))
                                        if pid != current_pid:  # 排除当前进程
                                            pids.append(pid)
                                    except ValueError:
                                        pass
                elif service == 'open-webui':
                    # 对于open-webui，使用更精确的查找方式
                    # 查找包含open-webui或webui serve的python进程
                    result = subprocess.run(['tasklist', '/fi', 'imagename eq python.exe', '/fo', 'csv'], capture_output=True, text=True)
                    for line in result.stdout.split('\n'):
                        if 'python.exe' in line:
                            parts = line.split(',')
                            if len(parts) > 1:
                                try:
                                    pid = int(parts[1].strip('"'))
                                    if pid != current_pid:  # 排除当前进程
                                        # 检查窗口标题或命令行参数是否包含webui
                                        result2 = subprocess.run(['tasklist', '/v', '/fo', 'csv', '/pid', str(pid)], capture_output=True, text=True)
                                        if 'webui' in result2.stdout.lower() or 'open-webui' in result2.stdout.lower():
                                            pids.append(pid)
                                except (ValueError, subprocess.SubprocessError):
                                    pass
                elif service == 'openclaw':
                    # 对于openclaw，查找gateway进程
                    result = subprocess.run(['tasklist', '/fi', 'imagename eq openclaw.exe', '/fo', 'csv'], capture_output=True, text=True)
                    for line in result.stdout.split('\n'):
                        if 'openclaw.exe' in line:
                            parts = line.split(',')
                            if len(parts) > 1:
                                try:
                                    pid = int(parts[1].strip('"'))
                                    if pid != current_pid:  # 排除当前进程
                                        pids.append(pid)
                                except ValueError:
                                    pass
        
        return pids
    
    def terminate_process(self, pid, service):
        """终止指定进程
        
        Args:
            pid: 进程ID
            service: 服务名称
        """
        # 在Windows上隐藏控制台窗口
        creation_flags = 0
        if os.name == 'nt':
            creation_flags = subprocess.CREATE_NO_WINDOW
        
        self.log_queues[service].put(("info", f"尝试终止进程 (PID: {pid})..."))
        
        # 直接使用强制终止（/F表示强制，/T表示终止进程树）
        # 这样可以确保一次杀死所有相关进程
        terminate_result = subprocess.run(
            ["taskkill", "/F", "/T", "/PID", str(pid)], 
            capture_output=True, 
            text=True, 
            creationflags=creation_flags
        )
        
        if terminate_result.returncode != 0:
            # 如果还是失败，输出错误信息
            error_msg = terminate_result.stderr.strip() if terminate_result.stderr else "未知错误"
            self.log_queues[service].put(("warning", f"终止进程失败: {error_msg}"))
            
            # 尝试使用另一种方式
            subprocess.run(
                ["taskkill", "/F", "/PID", str(pid)], 
                capture_output=True, 
                creationflags=creation_flags
            )

    def stop_service(self, service):
        """停止服务"""
        # 检查服务是否在运行
        if service not in self.processes:
            self.log_queues[service].put(("warning", f"{service} 服务未运行"))
            return
        
        try:
            process = self.processes[service]
            
            # 标记正在停止操作（防止read_process_output更新按钮状态）
            self.active_stop_operations.add(service)
            
            # 切换到对应的日志选项卡
            self.notebook.select(self.log_tabs[service])
            
            # 设置按钮为正在停止状态
            if service == "ollama":
                self.ollama_start_btn.config(text="正在停止...", state="disabled")
            elif service == "open-webui":
                self.webui_start_btn.config(text="正在停止...", state="disabled")
            elif service == "openclaw":
                self.claw_start_btn.config(text="正在停止...", state="disabled")
            
            self.log_queues[service].put(("info", f"=== 关闭 {service} 服务 ==="))
            
            # 检查是否是检测到的服务
            if process == "detected":
                # 查找服务的进程ID
                pids = self.find_service_pids(service)
                if pids:
                    self.log_queues[service].put(("info", f"找到 {service} 进程 (PID: {', '.join(map(str, pids))})..."))
                    for pid in pids:
                        self.terminate_process(pid, service)
                else:
                    self.log_queues[service].put(("warning", f"未找到 {service} 的进程"))
                    # 移除进程记录并更新按钮状态
                    del self.processes[service]
                    self._update_button_after_stop(service)
                    self.log_queues[service].put(("info", f"{service} 服务已关闭"))
                    return
            else:
                self.log_queues[service].put(("info", f"尝试终止进程 (PID: {process.pid})..."))
            
            # 对于 openclaw，先尝试使用命令停止
            if service == "openclaw":
                try:
                    # 在Windows上隐藏控制台窗口
                    creation_flags = 0
                    if os.name == 'nt':
                        creation_flags = subprocess.CREATE_NO_WINDOW
                    subprocess.run(["openclaw", "gateway", "stop"], capture_output=True, text=True, timeout=5, creationflags=creation_flags)
                    self.log_queues[service].put(("info", "已发送停止命令: openclaw gateway stop"))
                except subprocess.TimeoutExpired:
                    self.log_queues[service].put(("info", "命令超时，尝试其他方式终止..."))
                except FileNotFoundError:
                    self.log_queues[service].put(("info", "停止命令未找到，尝试其他方式终止..."))
                except Exception as e:
                    self.log_queues[service].put(("info", f"命令停止失败: {str(e)}，尝试其他方式终止..."))
            
            # 如果不是检测到的服务，使用进程对象终止
            if process != "detected":
                # 终止进程
                if os.name == 'nt':  # Windows
                    # 在Windows上隐藏控制台窗口
                    creation_flags = subprocess.CREATE_NO_WINDOW
                    
                    # 先尝试正常终止
                    terminate_result = subprocess.run(["taskkill", "/T", "/PID", str(process.pid)], capture_output=True, text=True, creationflags=creation_flags)
                    if terminate_result.returncode != 0:
                        # 如果失败，强制终止
                        self.log_queues[service].put(("info", "正常终止失败，尝试强制终止..."))
                        subprocess.run(["taskkill", "/F", "/T", "/PID", str(process.pid)], capture_output=True, creationflags=creation_flags)
                else:  # Unix
                    # 先尝试正常终止
                    process.terminate()
                    try:
                        # 等待进程结束
                        process.wait(timeout=3)
                        if process.poll() is None:
                            # 如果进程仍然运行，强制杀死
                            self.log_queues[service].put(("info", "进程仍在运行，尝试强制杀死..."))
                            process.kill()
                            process.wait(timeout=2)
                    except subprocess.TimeoutExpired:
                        # 超时，强制杀死
                        self.log_queues[service].put(("info", "终止超时，尝试强制杀死..."))
                        process.kill()
                        try:
                            process.wait(timeout=2)
                        except subprocess.TimeoutExpired:
                            self.log_queues[service].put(("error", "强制杀死进程超时"))
            
            # 移除进程记录
            if service in self.processes:
                del self.processes[service]
            
            # 延迟检查服务状态
            def check_stopped_status():
                """检查服务是否已停止"""
                time.sleep(2)  # 等待进程终止
                
                # 最多重试3次，每次查找并终止所有找到的进程
                for attempt in range(3):
                    if not self.check_service_running(service):
                        break
                    
                    # 再次检查服务是否还在运行
                    self.log_queues[service].put(("info", f"{service} 服务仍在运行，尝试第 {attempt + 1} 次停止..."))
                    
                    # 重新查找所有进程
                    pids = self.find_service_pids(service)
                    if pids:
                        self.log_queues[service].put(("info", f"找到 {service} 进程 (PID: {', '.join(map(str, pids))})..."))
                        for pid in pids:
                            self.terminate_process(pid, service)
                    else:
                        self.log_queues[service].put(("info", f"未找到更多 {service} 进程"))
                        break
                    
                    # 等待一下让进程完全终止
                    time.sleep(2)
                
                # 最终检查
                if not self.check_service_running(service):
                    self.log_queues[service].put(("info", f"{service} 服务已关闭"))
                    # 只有确认停止后才更新按钮
                    self._update_button_after_stop(service)
                else:
                    # 最后再尝试一次强制终止所有进程
                    pids = self.find_service_pids(service)
                    if pids:
                        self.log_queues[service].put(("warning", f"尝试强制终止所有 {service} 进程..."))
                        for pid in pids:
                            # 直接使用强制终止
                            if os.name == 'nt':
                                subprocess.run(
                                    ["taskkill", "/F", "/T", "/PID", str(pid)], 
                                    capture_output=True, 
                                    creationflags=subprocess.CREATE_NO_WINDOW
                                )
                    
                    time.sleep(1)
                    if not self.check_service_running(service):
                        self.log_queues[service].put(("info", f"{service} 服务已关闭"))
                        # 只有确认停止后才更新按钮
                        self._update_button_after_stop(service)
                    else:
                        self.log_queues[service].put(("error", f"无法完全停止 {service} 服务，按钮将保持关闭状态"))
                        # 服务未停止，按钮保持"关闭"状态
                        self._update_button_after_failure(service)
                
                # 移除停止操作标记
                self.active_stop_operations.discard(service)
            
            # 在后台线程中检查状态
            status_thread = threading.Thread(target=check_stopped_status, daemon=True)
            status_thread.start()
            
        except Exception as e:
            self.log_queues[service].put(("error", f"关闭服务失败: {str(e)}"))
            # 移除停止操作标记
            self.active_stop_operations.discard(service)
            # 恢复按钮状态
            self._update_button_after_failure(service)
            # 移除进程记录
            if service in self.processes:
                del self.processes[service]
    
    def _update_button_after_stop(self, service):
        """停止服务后更新按钮状态"""
        if service == "ollama":
            self.ollama_start_btn.config(text="启动", state="normal", style="Start.TButton")
        elif service == "open-webui":
            self.webui_start_btn.config(text="启动", state="normal", style="Start.TButton")
        elif service == "openclaw":
            self.claw_start_btn.config(text="启动", state="normal", style="Start.TButton")
    
    def _update_button_after_failure(self, service):
        """停止失败时更新按钮状态（保持关闭状态）"""
        if service == "ollama":
            self.ollama_start_btn.config(text="关闭", state="normal", style="Stop.TButton")
        elif service == "open-webui":
            self.webui_start_btn.config(text="关闭", state="normal", style="Stop.TButton")
        elif service == "openclaw":
            self.claw_start_btn.config(text="关闭", state="normal", style="Stop.TButton")
    
    def read_process_output(self, service, process):
        """读取进程输出"""
        try:
            while process.poll() is None:
                line = process.stdout.readline()
                if line:
                    self.log_queues[service].put(("info", line.strip()))
            
            # 读取剩余输出
            for line in process.stdout:
                self.log_queues[service].put(("info", line.strip()))
            
            # 检查进程退出状态
            exit_code = process.poll()
            if exit_code is not None:
                if exit_code == 0:
                    # 对于openclaw，启动命令本身执行成功就够了，服务在后台运行
                    if service == "openclaw":
                        self.log_queues[service].put(("info", f"启动命令已执行，请稍候..."))
                    else:
                        self.log_queues[service].put(("info", f"服务已正常退出"))
                else:
                    self.log_queues[service].put(("error", f"启动命令执行失败，返回码: {exit_code}"))
            
            # 清理进程记录
            if service in self.processes:
                del self.processes[service]
            
            # 延迟检查服务是否真的停止了
            def check_and_update():
                """检查服务状态并更新按钮"""
                time.sleep(1)
                # 检查服务是否还在运行
                if not self.check_service_running(service):
                    self.log_queues[service].put(("info", f"{service} 服务已关闭"))
                else:
                    # 如果服务还在运行，重新关联进程并更新按钮
                    pids = self.find_service_pids(service)
                    if pids:
                        # 标记为检测到的服务
                        self.processes[service] = "detected"
                        # 更新按钮为关闭状态
                        if service == "ollama":
                            self.ollama_start_btn.config(text="关闭", state="normal", style="Stop.TButton")
                        elif service == "open-webui":
                            self.webui_start_btn.config(text="关闭", state="normal", style="Stop.TButton")
                        elif service == "openclaw":
                            self.claw_start_btn.config(text="关闭", state="normal", style="Stop.TButton")
                        return
                
                # 服务已停止，不在这里更新按钮
                # 按钮更新由 stop_service 中的 check_stopped_status 统一处理
                # 如果 stop_service 没有被调用，才在这里更新
                if service not in self.active_stop_operations:
                    self._update_button_after_stop(service)
            
            # 在后台检查状态
            check_thread = threading.Thread(target=check_and_update, daemon=True)
            check_thread.start()
                    
        except Exception as e:
            self.log_queues[service].put(("error", f"读取日志失败: {str(e)}"))
            # 尝试恢复按钮状态
            self._update_button_after_stop(service)
    
    def update_logs(self):
        """更新日志显示"""
        while self.running:
            for service in self.log_queues:
                try:
                    while not self.log_queues[service].empty():
                        log_type, message = self.log_queues[service].get_nowait()
                        self.log_contents[service].append(message)
                        
                        # 限制日志行数
                        if len(self.log_contents[service]) > 1000:
                            self.log_contents[service] = self.log_contents[service][-1000:]
                        
                        # 更新日志显示
                        self.update_log_display(service)
                except queue.Empty:
                    pass
            time.sleep(0.1)
    
    def update_log_display(self, service):
        """更新日志显示"""
        if service not in self.log_texts:
            return
        text = self.log_texts[service]
        if text is None:
            return
        text.config(state=tk.NORMAL)
        text.delete(1.0, tk.END)
        text.insert(tk.END, "\n".join(self.log_contents.get(service, [])))
        text.see(tk.END)
        text.config(state=tk.DISABLED)
    
    def search_log(self, service, keyword):
        """搜索日志"""
        if not keyword:
            return
        
        if service not in self.log_texts:
            return
        text = self.log_texts[service]
        if text is None:
            return
        
        text.config(state=tk.NORMAL)
        text.tag_remove("search", 1.0, tk.END)
        
        # 搜索关键词
        start_pos = 1.0
        while True:
            pos = text.search(keyword, start_pos, stopindex=tk.END)
            if not pos:
                break
            end_pos = f"{pos}+{len(keyword)}c"
            text.tag_add("search", pos, end_pos)
            start_pos = end_pos
        
        # 设置搜索高亮
        text.tag_config("search", background="yellow", foreground="black")
        text.config(state=tk.DISABLED)
    
    def clear_log(self, service):
        """清除日志"""
        if service in self.log_contents:
            self.log_contents[service] = []
        if service in self.log_texts:
            self.update_log_display(service)
    
    def check_environment(self):
        """检查环境"""
        # 创建环境检查窗口
        check_window = tk.Toplevel(self.root)
        check_window.title("环境检查")
        check_window.geometry("900x600")
        check_window.resizable(False, False)
        
        # 上半部分：日志显示区域
        log_frame = ttk.Frame(check_window)
        log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 创建检查结果文本框
        text = tk.Text(log_frame, wrap=tk.WORD, state=tk.DISABLED, width=70, height=22)
        text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        scrollbar = ttk.Scrollbar(text, orient=tk.VERTICAL, command=text.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        text.config(yscrollcommand=scrollbar.set)
        
        # 下半部分：按钮区域
        btn_frame = ttk.Frame(check_window)
        btn_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
        
        # 开始检查按钮
        start_btn = ttk.Button(btn_frame, text="开始检查", command=lambda: self.run_environment_check(text, start_btn, install_frame))
        start_btn.pack(fill=tk.X, pady=2)
        
        # 安装按钮框架
        install_frame = ttk.LabelFrame(btn_frame, text="一键安装/更新", padding="5")
        install_frame.pack(fill=tk.X, pady=2)
        
        # 创建安装/更新按钮并保存引用
        self.ollama_btn = ttk.Button(install_frame, text="安装 Ollama", command=lambda: self.update_or_install("ollama", text, self.ollama_btn))
        self.ollama_btn.pack(side=tk.LEFT, padx=2, fill=tk.X, expand=True)
        
        self.webui_btn = ttk.Button(install_frame, text="安装 Open-WebUI", command=lambda: self.update_or_install("open-webui", text, self.webui_btn))
        self.webui_btn.pack(side=tk.LEFT, padx=2, fill=tk.X, expand=True)
        
        self.claw_btn = ttk.Button(install_frame, text="安装 OpenClaw", command=lambda: self.update_or_install("openclaw", text, self.claw_btn))
        self.claw_btn.pack(side=tk.LEFT, padx=2, fill=tk.X, expand=True)
        
        self.nodejs_btn = ttk.Button(install_frame, text="安装 Node.js", command=lambda: self.update_or_install("nodejs", text, self.nodejs_btn))
        self.nodejs_btn.pack(side=tk.LEFT, padx=2, fill=tk.X, expand=True)
    
    def check_ports(self, parent_window):
        """检测端口占用情况"""
        def update_port_status(msg, status="info"):
            port_text.config(state=tk.NORMAL)
            port_text.insert(tk.END, msg + "\n")
            port_text.see(tk.END)
            port_text.config(state=tk.NORMAL)
        
        # 解析地址配置
        def parse_host_port(host_var):
            """解析 host:port 格式"""
            try:
                host, port = host_var.get().split(':')
                return host.strip(), int(port.strip())
            except (ValueError, AttributeError):
                return None, None
        
        # 创建端口检测窗口
        port_window = tk.Toplevel(parent_window)
        port_window.title("端口占用检测")
        port_window.geometry("500x400")
        port_window.transient(parent_window)
        port_window.grab_set()
        
        port_text = tk.Text(port_window, wrap=tk.WORD, width=60, height=20)
        port_text.pack(fill=tk.BOTH, expand=True, padx=10, pady=(10, 0))
        
        scrollbar = ttk.Scrollbar(port_text, orient=tk.VERTICAL, command=port_text.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        port_text.config(yscrollcommand=scrollbar.set)
        
        # 倒计时标签
        countdown_frame = ttk.Frame(port_window)
        countdown_frame.pack(fill=tk.X, padx=10, pady=5)
        countdown_label = ttk.Label(countdown_frame, text="", foreground="#FF0000")
        countdown_label.pack()
        
        # 倒计时函数
        def update_countdown(remaining):
            if remaining > 0:
                countdown_label.config(text=f"窗口将在 {remaining} 秒后自动关闭...")
                port_window.after(1000, lambda: update_countdown(remaining - 1))
            else:
                port_window.destroy()
        
        # 启动检测线程
        def perform_port_check():
            update_port_status("=== 检测端口占用情况 ===\n")
            
            services = [
                ("Ollama", self.ollama_host_var),
                ("Open-WebUI", self.webui_host_var),
                ("OpenClaw", self.claw_host_var)
            ]
            
            for name, host_var in services:
                host, port = parse_host_port(host_var)
                if host and port:
                    update_port_status(f"{name} ({host}:{port}):")
                    if self.is_port_in_use(host, port):
                        update_port_status(f"  ✗ 端口 {port} 已被占用!", "error")
                    else:
                        update_port_status(f"  ✓ 端口 {port} 可用", "success")
                    update_port_status("")
                else:
                    update_port_status(f"{name}: 配置格式错误，应为 host:port\n", "warning")
            
            update_port_status("=== 检测完成 ===\n")
            
            # 开始3秒倒计时
            update_countdown(3)
        
        threading.Thread(target=perform_port_check, daemon=True).start()
    
    def is_port_in_use(self, host, port):
        """检查端口是否被占用
        
        Args:
            host: 主机地址
            port: 端口号
            
        Returns:
            bool: 端口是否被占用
        """
        import socket
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(1)
        try:
            result = sock.connect_ex((host, port))
            return result == 0
        except socket.error:
            return False
        finally:
            sock.close()
    
    def run_environment_check(self, text, btn, install_frame):
        """运行环境检查"""
        # 禁用按钮
        btn.config(state=tk.DISABLED)
        
        # 清空文本
        text.config(state=tk.NORMAL)
        text.delete(1.0, tk.END)
        text.insert(tk.END, "开始检查环境...\n\n")
        text.config(state=tk.DISABLED)
        
        # 启动检查线程
        check_thread = threading.Thread(target=self.perform_environment_check, args=(text, btn, install_frame), daemon=True)
        check_thread.start()
    
    def perform_environment_check(self, text, btn, install_frame):
        """执行环境检查"""
        def update_text(message):
            if not self.running:
                return
            try:
                text.config(state=tk.NORMAL)
                text.insert(tk.END, message + "\n")
                text.see(tk.END)
                text.config(state=tk.DISABLED)
            except Exception:
                pass
        
        # 保存各软件的安装状态
        ollama_installed = False
        webui_installed = False
        claw_installed = False
        nodejs_installed = False
        
        try:
            # 检查 Ollama - 使用 cmd /c 方式解决 PATH 问题
            update_text("=== 检查 Ollama - 版本 ===")
            try:
                result = subprocess.run(["cmd", "/c", "ollama --version"], capture_output=True, text=True)
                if result.returncode == 0:
                    output = result.stdout.strip()
                    lines = [line for line in output.split('\n') if line.strip() and not line.strip().startswith('Warning')]
                    if lines:
                        version = lines[-1].strip()
                        update_text(f"✓ Ollama 已安装: {version}")
                        ollama_installed = True
                    else:
                        update_text(f"✓ Ollama 已安装: {output}")
                        ollama_installed = True
                else:
                    update_text("✗ Ollama 未安装")
            except Exception:
                update_text("✗ Ollama 未安装")
            
            # 检查 Open-WebUI
            update_text("\n=== 检查 Open-WebUI - 版本 ===")
            try:
                result = subprocess.run([sys.executable, "-m", "pip", "show", "open-webui"], capture_output=True, text=True)
                if result.returncode == 0:
                    version_match = re.search(r"Version: (.*)", result.stdout)
                    if version_match:
                        update_text(f"✓ Open-WebUI 已安装: {version_match.group(1)}")
                        webui_installed = True
                    else:
                        update_text(f"✓ Open-WebUI 已安装")
                        webui_installed = True
                else:
                    update_text("✗ Open-WebUI 未安装")
            except Exception:
                update_text("✗ Open-WebUI 未安装")
            
            # 检查 OpenClaw - 使用 cmd /c 方式解决 PATH 问题
            update_text("\n=== 检查 OpenClaw - 版本 ===")
            openclaw_found = False
            try:
                result = subprocess.run(["cmd", "/c", "openclaw --version"], capture_output=True, text=True)
                if result.returncode == 0:
                    output = result.stdout.strip() or result.stderr.strip()
                    if output:
                        update_text(f"✓ OpenClaw 已安装: {output}")
                    else:
                        update_text(f"✓ OpenClaw 已安装")
                    openclaw_found = True
                elif result.stderr and ("not found" in result.stderr.lower() or "not recognized" in result.stderr.lower()):
                    pass
                else:
                    stderr = result.stderr.strip() if result.stderr else ""
                    if stderr and len(stderr) > 10 and "-" in stderr:
                        update_text(f"✓ OpenClaw 已安装: {stderr}")
                        openclaw_found = True
            except FileNotFoundError:
                pass
            except Exception:
                pass
            
            if openclaw_found:
                claw_installed = True
            else:
                update_text("✗ OpenClaw 未安装")
                update_text("  请点击【安装 OpenClaw】按钮进行安装")
            
            # 检查 Node.js / npm - 使用 cmd /c 方式解决 PATH 问题
            update_text("\n=== 检查 Node.js / npm ===")
            try:
                result = subprocess.run(["cmd", "/c", "node --version"], capture_output=True, text=True)
                node_version = result.stdout.strip() if result.returncode == 0 else None
                
                result = subprocess.run(["cmd", "/c", "npm --version"], capture_output=True, text=True)
                npm_version = result.stdout.strip() if result.returncode == 0 else None
                
                if node_version and npm_version:
                    update_text(f"✓ Node.js 已安装: {node_version}")
                    update_text(f"✓ npm 已安装: {npm_version}")
                    nodejs_installed = True
                elif npm_version:
                    update_text(f"✓ npm 已安装: {npm_version}")
                    update_text("  (Node.js 版本信息获取失败)")
                else:
                    update_text("✗ Node.js / npm 未安装")
            except Exception:
                update_text("✗ Node.js / npm 未安装")
            
            update_text("\n环境检查完成！")
            
        except Exception as e:
            update_text(f"检查过程中出错: {str(e)}")
        finally:
            # 启用按钮
            try:
                if btn and btn.winfo_exists():
                    btn.config(state=tk.NORMAL)
            except Exception:
                pass
            
            # 根据检查结果更新按钮文字
            def update_button_texts():
                if not self.running:
                    return
                try:
                    if ollama_installed:
                        self.ollama_btn.config(text="更新 Ollama")
                    else:
                        self.ollama_btn.config(text="安装 Ollama")
                    
                    if webui_installed:
                        self.webui_btn.config(text="更新 Open-WebUI")
                    else:
                        self.webui_btn.config(text="安装 Open-WebUI")
                    
                    if claw_installed:
                        self.claw_btn.config(text="更新 OpenClaw")
                    else:
                        self.claw_btn.config(text="安装 OpenClaw")
                    
                    if nodejs_installed:
                        self.nodejs_btn.config(text="更新 Node.js")
                    else:
                        self.nodejs_btn.config(text="安装 Node.js")
                except Exception:
                    pass
            
            # 在主线程中更新按钮文字
            if self.running:
                install_frame.after(0, update_button_texts)
    
    def install_component(self, component, text):
        """安装组件"""
        # 启动安装线程
        install_thread = threading.Thread(target=self.perform_install, args=(component, text), daemon=True)
        install_thread.start()
    
    def update_or_install(self, component, text, btn):
        """更新或安装组件 - 先检查版本，弹出确认框后再执行"""
        # 获取当前版本
        def get_current_version(comp):
            """获取当前安装的版本"""
            creation_flags = 0
            if os.name == 'nt':
                creation_flags = subprocess.CREATE_NO_WINDOW
            system_env = os.environ.copy()
            
            try:
                if comp == "ollama":
                    result = subprocess.run(["cmd", "/c", "ollama --version"], capture_output=True, text=True, timeout=10, creationflags=creation_flags, env=system_env)
                    if result.returncode == 0:
                        return result.stdout.strip() or result.stderr.strip()
                elif comp == "open-webui":
                    result = subprocess.run([sys.executable, "-m", "pip", "show", "open-webui"], capture_output=True, text=True, timeout=10, creationflags=creation_flags, env=system_env)
                    if result.returncode == 0:
                        import re
                        match = re.search(r"Version: (.*)", result.stdout)
                        if match:
                            return match.group(1)
                elif comp == "openclaw":
                    result = subprocess.run(["cmd", "/c", "openclaw --version"], capture_output=True, text=True, timeout=10, creationflags=creation_flags, env=system_env)
                    if result.returncode == 0:
                        return result.stdout.strip() or result.stderr.strip()
                elif comp == "nodejs":
                    result = subprocess.run(["cmd", "/c", "node --version"], capture_output=True, text=True, timeout=10, creationflags=creation_flags, env=system_env)
                    if result.returncode == 0:
                        return result.stdout.strip()
            except Exception:
                pass
            return None
        
        def get_latest_version(comp):
            """获取服务器上的最新版本"""
            creation_flags = 0
            if os.name == 'nt':
                creation_flags = subprocess.CREATE_NO_WINDOW
            system_env = os.environ.copy()
            
            try:
                if comp == "ollama":
                    # Ollama版本从GitHub API获取
                    import urllib.request
                    try:
                        req = urllib.request.Request(
                            "https://api.github.com/repos/ollama/ollama/releases/latest",
                            headers={"User-Agent": "Mozilla/5.0", "Accept": "application/vnd.github.v3+json"}
                        )
                        with urllib.request.urlopen(req, timeout=10) as response:
                            import json
                            data = json.loads(response.read().decode('utf-8'))
                            tag_name = data.get('tag_name', '')
                            # 去掉 'v' 前缀
                            if tag_name.startswith('v'):
                                tag_name = tag_name[1:]
                            if tag_name and re.match(r'^\d+\.\d+', tag_name):
                                return tag_name
                    except Exception:
                        pass
                    # 如果无法获取，使用当前版本
                    return current_version
                elif comp == "open-webui":
                    result = subprocess.run([sys.executable, "-m", "pip", "index", "versions", "open-webui"], capture_output=True, text=True, timeout=30, creationflags=creation_flags, env=system_env)
                    if result.returncode == 0:
                        import re
                        match = re.search(r"Available versions: (.*)", result.stdout, re.DOTALL)
                        if match:
                            versions = match.group(1).strip().split(",")
                            if versions:
                                return versions[0].strip()
                elif comp == "openclaw":
                    result = subprocess.run(["cmd", "/c", "npm view openclaw version"], capture_output=True, text=True, timeout=30, creationflags=creation_flags, env=system_env)
                    if result.returncode == 0:
                        return result.stdout.strip()
                elif comp == "nodejs":
                    result = subprocess.run(["cmd", "/c", "npm view node version"], capture_output=True, text=True, timeout=30, creationflags=creation_flags, env=system_env)
                    if result.returncode == 0:
                        version = result.stdout.strip()
                        if version.startswith('v'):
                            return version[1:]
                        return version
            except Exception:
                pass
            return None
        
        # 标准化版本号（去掉 v 前缀）
        def normalize_version(v):
            """标准化版本号用于比较"""
            if not v:
                return ""
            v = v.strip()
            if v.startswith('v'):
                v = v[1:]
            return v
        
        # 获取当前版本
        current_version = get_current_version(component)
        current_version_normalized = normalize_version(current_version)
        
        # 确定组件显示名称
        comp_names = {
            "ollama": "Ollama",
            "open-webui": "Open-WebUI",
            "openclaw": "OpenClaw",
            "nodejs": "Node.js"
        }
        comp_name = comp_names.get(component, component)
        
        # 如果已安装，检查是否有新版本
        if current_version:
            # 获取最新版本
            latest_version = get_latest_version(component)
            latest_version_normalized = normalize_version(latest_version) if latest_version else None
            
            if latest_version_normalized and latest_version_normalized != current_version_normalized:
                # 有新版本，弹出确认框
                message = f"当前版本: {current_version}\n最新版本: {latest_version}\n\n是否更新到最新版本？"
                confirm = messagebox.askyesno(f"更新 {comp_name}", message)
                if confirm:
                    self.install_component(component, text)
                return
            elif latest_version_normalized == current_version_normalized:
                messagebox.showinfo(f"{comp_name} 已是最新版本", f"当前版本 {current_version} 已是最新版本")
                return
            else:
                messagebox.showinfo(f"检查 {comp_name} 版本失败", f"无法获取最新版本，当前版本: {current_version}\n点击确定继续安装...")
        
        # 未安装或版本检查失败，直接安装
        message = f"未检测到 {comp_name} 或版本检查失败，是否安装？"
        confirm = messagebox.askyesno(f"安装 {comp_name}", message)
        if confirm:
            self.install_component(component, text)
    
    def perform_install(self, component, text):
        """执行组件安装"""
        def update_text(message):
            if not self.running:
                return
            try:
                text.config(state=tk.NORMAL)
                text.insert(tk.END, message + "\n")
                text.see(tk.END)
                text.config(state=tk.NORMAL)
            except Exception:
                pass
        
        # 检测操作系统
        import platform
        system = platform.system().lower()
        
        # 在Windows上隐藏控制台窗口
        creation_flags = 0
        if os.name == 'nt':
            creation_flags = subprocess.CREATE_NO_WINDOW
        
        # 使用系统的环境变量
        system_env = os.environ.copy()
        
        def get_software_version(check_cmd, version_patterns):
            """获取软件版本号"""
            try:
                result = subprocess.run(
                    check_cmd,
                    capture_output=True, text=True, timeout=10,
                    creationflags=creation_flags,
                    env=system_env
                )
                output = (result.stdout + result.stderr).strip()
                for pattern in version_patterns:
                    import re
                    match = re.search(pattern, output, re.IGNORECASE)
                    if match:
                        return match.group(1) if match.groups() else match.group(0)
                return output[:50] if output else "未知"
            except Exception:
                return None
        
        def run_with_cmd_check(cmd):
            """使用 cmd.exe 执行命令，解决 PATH 问题"""
            try:
                result = subprocess.run(
                    ["cmd", "/c", cmd],
                    capture_output=True, text=True, timeout=10,
                    creationflags=creation_flags,
                    env=system_env
                )
                return result.returncode == 0, result.stdout.strip() or result.stderr.strip()
            except Exception:
                return False, ""
        
        def check_npm_exists():
            """检查 npm 是否已安装"""
            exists, version = run_with_cmd_check("npm --version")
            if exists and version:
                return True, version
            return False, None
        
        def check_nodejs_exists():
            """检查 Node.js 是否已安装"""
            exists, version = run_with_cmd_check("node --version")
            if exists and version:
                return True, version
            return False, None
        
        def check_ollama_exists():
            """检查 Ollama 是否已安装"""
            exists, output = run_with_cmd_check("ollama --version")
            if exists and output:
                # 过滤警告信息，只保留版本号
                import re
                version_match = re.search(r'(\d+\.\d+\.\d+)', output)
                if version_match:
                    return True, version_match.group(1)
                elif 'Warning' not in output:
                    return True, output
            return False, None
        
        def check_openwebui_exists():
            """检查 Open-WebUI 是否已安装"""
            try:
                result = subprocess.run(
                    [sys.executable, "-m", "pip", "show", "open-webui"],
                    capture_output=True, text=True, timeout=10,
                    creationflags=creation_flags,
                    env=system_env
                )
                if result.returncode == 0:
                    for line in result.stdout.split('\n'):
                        if line.startswith('Version:'):
                            return True, line.replace('Version:', '').strip()
                    return True, "已安装"
                return False, None
            except Exception:
                return False, None
        
        def check_openclaw_exists():
            """检查 OpenClaw 是否已安装"""
            exists, version = run_with_cmd_check("openclaw --version")
            if exists and version:
                # 检查配置文件是否存在
                if self.claw_config_file.exists():
                    return True, version
                else:
                    return False, None
            return False, None
        
        def check_python_exists():
            """检查 Python 是否已安装"""
            try:
                result = subprocess.run(
                    [sys.executable, "--version"],
                    capture_output=True, text=True, timeout=10,
                    creationflags=creation_flags,
                    env=system_env
                )
                output = result.stdout.strip() or result.stderr.strip()
                import re
                match = re.search(r'(\d+\.\d+\.\d+)', output)
                if match:
                    return match.group(1)
            except Exception:
                pass
            return None
        
        def check_git_exists():
            """检查 Git 是否已安装"""
            exists, version = run_with_cmd_check("git --version")
            if exists and version:
                return version
            return None
        
        def check_chocolatey_exists():
            """检查 Chocolatey 是否已安装"""
            exists, version = run_with_cmd_check("choco --version")
            if exists and version:
                return version
            return None
        
        # 获取包管理器检查命令
        def check_package_manager(managers):
            """检查可用的包管理器"""
            for manager in managers:
                result = subprocess.run(
                    manager["check"], 
                    capture_output=True, text=True,
                    creationflags=creation_flags
                )
                if result.returncode == 0:
                    return manager
            return None
        
        def install_chocolatey(update_func):
            """自动安装 Chocolatey"""
            update_func("未检测到 Chocolatey，正在自动安装...")
            update_func("正在下载并安装 Chocolatey...")
            
            try:
                # 使用 PowerShell 安装 Chocolatey
                install_script = """
                Set-ExecutionPolicy Bypass -Scope Process -Force;
                iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
                """
                result = subprocess.run(
                    ["powershell", "-Command", install_script],
                    capture_output=True, text=True, timeout=600,
                    creationflags=creation_flags
                )
                
                if result.returncode == 0:
                    update_func("✓ Chocolatey 安装成功")
                    # 刷新环境变量
                    subprocess.run(
                        ["refreshenv"],
                        capture_output=True, text=True,
                        creationflags=creation_flags
                    )
                    return True
                else:
                    update_func("✗ Chocolatey 安装失败")
                    if result.stderr:
                        update_func(f"错误: {result.stderr[-200:]}")
                    return False
            except Exception as e:
                update_func(f"✗ 安装 Chocolatey 时出错: {str(e)}")
                return False
        
        try:
            if component == "ollama":
                update_text("=== 安装 Ollama ===")
                
                # 先检查 Ollama 是否已安装
                update_text("正在检查 Ollama 安装状态...")
                ollama_exists, ollama_version = check_ollama_exists()
                
                if ollama_exists:
                    update_text(f"✓ Ollama 已安装: {ollama_version}")
                    update_text("如需重新安装，请先手动卸载现有版本")
                    update_text("访问 https://ollama.com/download 获取卸载说明")
                else:
                    update_text("✗ Ollama 未安装，即将开始安装...")
                    
                    if system == "windows":
                        # Windows: 使用 Chocolatey
                        update_text("检测到操作系统: Windows")
                        update_text("正在检查 Chocolatey...")
                        choco_version = check_chocolatey_exists()
                        if choco_version:
                            update_text(f"找到 Chocolatey ({choco_version})，正在安装 Ollama...")
                            result = subprocess.run(
                                ["choco", "install", "ollama", "-y"],
                                capture_output=True, text=True, timeout=300,
                                creationflags=creation_flags
                            )
                            if result.returncode == 0:
                                update_text("✓ Ollama 安装成功")
                                # 验证安装
                                ollama_exists, ollama_version = check_ollama_exists()
                                if ollama_exists:
                                    update_text(f"安装版本: {ollama_version}")
                            else:
                                update_text("Chocolatey 安装 Ollama 失败，尝试手动下载...")
                                self._open_download_page(text, "https://ollama.com/download", "Ollama")
                        else:
                            # 自动安装 Chocolatey
                            choco_installed = install_chocolatey(update_text)
                            if choco_installed:
                                update_text("Chocolatey 安装成功，正在安装 Ollama...")
                                result = subprocess.run(
                                    ["choco", "install", "ollama", "-y"],
                                    capture_output=True, text=True, timeout=300,
                                    creationflags=creation_flags
                                )
                                if result.returncode == 0:
                                    update_text("✓ Ollama 安装成功")
                                else:
                                    update_text("安装 Ollama 失败，尝试手动下载...")
                                    self._open_download_page(text, "https://ollama.com/download", "Ollama")
                            else:
                                update_text("无法自动安装 Chocolatey，尝试手动下载...")
                                self._open_download_page(text, "https://ollama.com/download", "Ollama")
                    
                    elif system == "darwin":
                        # macOS: 使用 brew
                        update_text("检测到操作系统: macOS")
                        update_text("正在尝试使用 Homebrew 安装...")
                        brew_result = subprocess.run(
                            ["brew", "--version"], 
                            capture_output=True, text=True,
                            creationflags=creation_flags
                        )
                        if brew_result.returncode == 0:
                            result = subprocess.run(
                                ["brew", "install", "ollama"],
                                capture_output=True, text=True, timeout=300,
                                creationflags=creation_flags
                            )
                            if result.returncode == 0:
                                update_text("✓ Ollama 安装成功")
                            else:
                                update_text("Homebrew 安装失败，尝试手动下载...")
                                self._open_download_page(text, "https://ollama.com/download", "Ollama")
                        else:
                            update_text("未检测到 Homebrew，请先安装 Homebrew")
                            update_text("访问 https://brew.sh 获取安装说明")
                    
                    elif system == "linux":
                        # Linux: 使用官方安装脚本
                        update_text("检测到操作系统: Linux")
                        update_text("正在使用官方脚本安装 Ollama...")
                        
                        # Ollama 官方安装脚本
                        result = subprocess.run(
                            "curl -fsSL https://ollama.com/install.sh | sh",
                            capture_output=True, text=True, timeout=300, shell=True,
                            creationflags=creation_flags
                        )
                        if result.returncode == 0:
                            update_text("✓ Ollama 安装成功")
                        else:
                            update_text("安装失败，请手动安装")
                            update_text("运行命令: curl -fsSL https://ollama.com/install.sh | sh")
                    
                    else:
                        update_text(f"不支持的操作系统: {system}")
                        self._open_download_page(text, "https://ollama.com/download", "Ollama")
            
            elif component == "open-webui":
                update_text("=== 安装 Open-WebUI ===")
                
                # 先检查 Open-WebUI 是否已安装
                update_text("正在检查 Open-WebUI 安装状态...")
                webui_exists, webui_version = check_openwebui_exists()
                
                if webui_exists:
                    update_text(f"✓ Open-WebUI 已安装: {webui_version}")
                    update_text("如需更新，请使用【更新】按钮或运行: pip install -U open-webui")
                else:
                    update_text("✗ Open-WebUI 未安装，即将开始安装...")
                    update_text("正在安装 Open-WebUI (使用 pip)...")
                    
                    # Open-WebUI 使用 pip 安装（跨平台）
                    result = subprocess.run(
                        [sys.executable, "-m", "pip", "install", "open-webui", "-U"],
                        capture_output=True, text=True, timeout=300,
                        creationflags=creation_flags
                    )
                    if result.returncode == 0:
                        update_text("✓ Open-WebUI 安装成功")
                        # 验证安装
                        webui_exists, webui_version = check_openwebui_exists()
                        if webui_exists:
                            update_text(f"安装版本: {webui_version}")
                    else:
                        update_text("✗ Open-WebUI 安装失败")
                        if result.stderr:
                            for line in result.stderr.strip().split('\n')[-3:]:
                                if line.strip():
                                    update_text(f"  {line}")
            
            elif component == "openclaw":
                update_text("=== 安装 OpenClaw ===")
                
                # 先检查 npm 是否已安装
                update_text("正在检查依赖环境...")
                npm_exists, npm_version = check_npm_exists()
                
                if not npm_exists:
                    update_text("✗ npm 未安装，请先安装 Node.js")
                    update_text("请先点击【安装 Node.js】按钮")
                    return
                else:
                    update_text(f"✓ npm 已安装 ({npm_version})")
                
                # 检查 OpenClaw 是否已安装
                update_text("正在检查 OpenClaw 安装状态...")
                claw_exists, claw_version = check_openclaw_exists()
                
                if claw_exists:
                    update_text(f"✓ OpenClaw 已安装: {claw_version}")
                    update_text("如需更新，请使用【更新】按钮或运行: npm update -g openclaw")
                else:
                    update_text("✗ OpenClaw 未安装，即将开始安装...")
                    
                    update_text("正在安装 OpenClaw (使用 npm)...")
                    result = subprocess.run(
                        ["npm", "install", "-g", "openclaw@latest"],
                        capture_output=True, text=True, timeout=300,
                        creationflags=creation_flags
                    )
                    if result.returncode == 0:
                        update_text("✓ OpenClaw 安装成功")
                        
                        # 创建配置文件
                        import json
                        (HOME_DIR / ".openclaw").mkdir(parents=True, exist_ok=True)
                        claw_config = {
                            "version": "1.0",
                            "gateway": {
                                "mode": "local",
                                "bind": "loopback",
                                "port": 18789
                            },
                            "services": {}
                        }
                        self.claw_config_file.write_text(json.dumps(claw_config, indent=2, ensure_ascii=False))
                        update_text(f"✓ 配置文件已创建: {self.claw_config_file}")
                        
                        # 验证安装
                        claw_exists, claw_version = check_openclaw_exists()
                        if claw_exists:
                            update_text(f"安装版本: {claw_version}")
                    else:
                        update_text("✗ OpenClaw 安装失败")
                        if result.stderr:
                            for line in result.stderr.strip().split('\n')[-3:]:
                                if line.strip():
                                    update_text(f"  {line}")
            
            elif component == "nodejs":
                update_text("=== 安装 Node.js ===")
                
                # 先检查 Node.js 是否已安装
                update_text("正在检查 Node.js 安装状态...")
                node_exists, node_version = check_nodejs_exists()
                
                if node_exists:
                    update_text(f"✓ Node.js 已安装: {node_version}")
                    update_text("如需更新，请访问 https://nodejs.org 下载新版本")
                else:
                    update_text("✗ Node.js 未安装，即将开始安装...")
                    
                    if system == "windows":
                        # Windows: 使用 Chocolatey
                        update_text("检测到操作系统: Windows")
                        update_text("正在检查 Chocolatey...")
                        choco_version = check_chocolatey_exists()
                        if choco_version:
                            update_text(f"找到 Chocolatey ({choco_version})，正在安装 Node.js...")
                            result = subprocess.run(
                                ["choco", "install", "nodejs", "-y"],
                                capture_output=True, text=True, timeout=300,
                                creationflags=creation_flags
                            )
                            if result.returncode == 0:
                                update_text("✓ Node.js 安装成功")
                                # 验证安装
                                node_exists, node_version = check_nodejs_exists()
                                if node_exists:
                                    update_text(f"安装版本: {node_version}")
                            else:
                                update_text("Chocolatey 安装 Node.js 失败，尝试手动下载...")
                                self._open_download_page(text, "https://nodejs.org", "Node.js")
                        else:
                            # 自动安装 Chocolatey
                            choco_installed = install_chocolatey(update_text)
                            if choco_installed:
                                update_text("Chocolatey 安装成功，正在安装 Node.js...")
                                result = subprocess.run(
                                    ["choco", "install", "nodejs", "-y"],
                                    capture_output=True, text=True, timeout=300,
                                    creationflags=creation_flags
                                )
                                if result.returncode == 0:
                                    update_text("✓ Node.js 安装成功")
                                else:
                                    update_text("安装 Node.js 失败，尝试手动下载...")
                                    self._open_download_page(text, "https://nodejs.org", "Node.js")
                            else:
                                update_text("无法自动安装 Chocolatey，尝试手动下载...")
                                self._open_download_page(text, "https://nodejs.org", "Node.js")
                    
                    elif system == "darwin":
                        # macOS: 使用 brew
                        update_text("检测到操作系统: macOS")
                        update_text("正在尝试使用 Homebrew 安装...")
                        brew_result = subprocess.run(
                            ["brew", "--version"], 
                            capture_output=True, text=True,
                            creationflags=creation_flags
                        )
                        if brew_result.returncode == 0:
                            result = subprocess.run(
                                ["brew", "install", "node"],
                                capture_output=True, text=True, timeout=300,
                                creationflags=creation_flags
                            )
                            if result.returncode == 0:
                                update_text("✓ Node.js 安装成功")
                            else:
                                update_text("Homebrew 安装失败，尝试手动下载...")
                                self._open_download_page(text, "https://nodejs.org", "Node.js")
                        else:
                            update_text("未检测到 Homebrew，请先安装 Homebrew")
                            update_text("访问 https://brew.sh 获取安装说明")
                    
                    elif system == "linux":
                        # Linux: 使用官方脚本
                        update_text("检测到操作系统: Linux")
                        update_text("正在使用官方脚本安装 Node.js...")
                        
                        # NodeSource LTS 安装脚本
                        result = subprocess.run(
                            "curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -",
                            capture_output=True, text=True, timeout=300, shell=True,
                            creationflags=creation_flags
                        )
                        if result.returncode == 0:
                            # 安装 nodejs
                            install_result = subprocess.run(
                                ["apt-get", "install", "-y", "nodejs"],
                                capture_output=True, text=True, timeout=300,
                                creationflags=creation_flags
                            )
                            if install_result.returncode == 0:
                                update_text("✓ Node.js 安装成功")
                            else:
                                update_text("安装失败，请手动安装")
                        else:
                            update_text("安装失败，请手动安装")
                            update_text("运行命令:")
                            update_text("  curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -")
                            update_text("  apt-get install -y nodejs")
                    
                    else:
                        update_text(f"不支持的操作系统: {system}")
                        self._open_download_page(text, "https://nodejs.org", "Node.js")
            
            update_text("\n安装操作完成！")
            
        except subprocess.TimeoutExpired:
            update_text("\n✗ 安装超时，请手动检查安装状态")
        except Exception as e:
            update_text(f"\n安装过程中出错: {str(e)}")
    
    def _open_download_page(self, text_widget, url, name):
        """打开下载页面"""
        import webbrowser
        text_widget.insert(tk.END, f"正在打开 {name} 下载页面...\n")
        text_widget.see(tk.END)
        try:
            webbrowser.open(url)
            text_widget.insert(tk.END, f"已打开 {name} 下载页面，请下载并安装\n")
            text_widget.see(tk.END)
        except:
            text_widget.insert(tk.END, f"无法自动打开，请访问 {url} 下载并安装\n")
            text_widget.see(tk.END)

def is_admin():
    """检查是否以管理员权限运行"""
    import os
    import ctypes
    
    if sys.platform == "win32":
        # Windows: 使用 Shell32 API
        try:
            return ctypes.windll.shell32.IsUserAnAdmin() == 1
        except:
            return False
    elif sys.platform == "darwin":
        # macOS: 检查是否为 root 用户
        return os.geteuid() == 0
    else:
        # Linux: 检查是否为 root 用户
        return os.geteuid() == 0

def main():
    """主函数"""
    # 检查管理员权限
    # if not is_admin():
    #     # 提示用户需要管理员权限
    #     print("警告：此脚本需要管理员权限才能运行！")
    #     # messagebox.showwarning("权限提示", "此脚本需要管理员权限才能运行！")

    #     if sys.platform == "win32":
    #         # Windows: 提示用户并尝试以管理员身份重新启动
    #         try:
    #             script = os.path.abspath(__file__)
    #             result = ctypes.windll.shell32.ShellExecuteW(
    #                 None, "runas", sys.executable, script, None, 1
    #             )
    #             if result <= 32:
    #                 raise Exception(f"ShellExecuteW 返回错误码: {result}")
    #             sys.exit(0)
    #         except Exception as e:
    #             messagebox.showwarning("权限提示", f"无法以管理员身份重新启动: {e}\n\n请手动右键点击程序，选择'以管理员身份运行'")
    #             sys.exit(1)
    #     else:
    #         # macOS/Linux: 提示用户需要 root 权限
    #         if sys.platform == "darwin":
    #             messagebox.showwarning("权限提示", "请使用 sudo 以管理员权限运行此脚本。\n\n例如: sudo python3 service_controller.py")
    #         else:
    #             messagebox.showwarning("权限提示", "请使用 sudo 以管理员权限运行此脚本。\n\n例如: sudo python3 service_controller.py")
    #         sys.exit(1)
    
    root = tk.Tk()
    app = ServiceController(root)
    root.mainloop()

if __name__ == "__main__":
    main()
