From 6671f590e97461867b2ad707a4192149a855c2f8 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 11 Jul 2025 15:14:52 +0800 Subject: [PATCH] fix --- docs/EMBEDDED_PYTHON_GUIDE.md | 224 +++++++++++ docs/PYTHON_ENV_MANAGER_GUIDE.md | 206 ++++++++++ scripts/demo_python_env_manager.bat | 66 +++ scripts/setup_embedded_python.bat | 40 ++ scripts/setup_embedded_python.py | 286 +++++++++++++ scripts/test_embedded_python.bat | 71 ++++ src-tauri/src/commands/mod.rs | 1 + src-tauri/src/commands/python_env_manager.rs | 295 ++++++++++++++ src-tauri/src/lib.rs | 8 +- src-tauri/src/python_executor.rs | 160 +++++++- src/App.tsx | 2 + src/components/Sidebar.tsx | 3 +- src/pages/PythonEnvManagerPage.tsx | 400 +++++++++++++++++++ 13 files changed, 1738 insertions(+), 24 deletions(-) create mode 100644 docs/EMBEDDED_PYTHON_GUIDE.md create mode 100644 docs/PYTHON_ENV_MANAGER_GUIDE.md create mode 100644 scripts/demo_python_env_manager.bat create mode 100644 scripts/setup_embedded_python.bat create mode 100644 scripts/setup_embedded_python.py create mode 100644 scripts/test_embedded_python.bat create mode 100644 src-tauri/src/commands/python_env_manager.rs create mode 100644 src/pages/PythonEnvManagerPage.tsx diff --git a/docs/EMBEDDED_PYTHON_GUIDE.md b/docs/EMBEDDED_PYTHON_GUIDE.md new file mode 100644 index 0000000..32b4cd4 --- /dev/null +++ b/docs/EMBEDDED_PYTHON_GUIDE.md @@ -0,0 +1,224 @@ +# 嵌入式Python方案指南 + +## 🎯 方案概述 + +这个方案将Python环境直接打包到你的Tauri应用中,用户无需安装Python即可使用所有功能。 + +## 🚀 快速开始 + +### 1. 设置嵌入式Python环境 + +```cmd +# 运行设置脚本 +scripts\setup_embedded_python.bat +``` + +这将: +- 下载Python 3.11嵌入式版本 +- 安装必要的依赖(requests, Pillow等) +- 复制你的python_core模块 +- 创建启动脚本 +- 更新Tauri配置 + +### 2. 测试嵌入式Python + +```cmd +# 测试Python版本 +src-tauri\python-embed\python.exe --version + +# 测试依赖 +src-tauri\python-embed\python.exe -c "import requests, PIL; print('Dependencies OK')" + +# 测试python_core模块 +src-tauri\python-embed\python.exe -c "import python_core; print('python_core OK')" +``` + +### 3. 启动应用测试 + +```cmd +cargo tauri dev +``` + +现在应用会自动使用嵌入式Python,无需系统Python环境。 + +## 📁 目录结构 + +设置完成后,你的项目结构如下: + +``` +your-project/ +├── src-tauri/ +│ ├── python-embed/ # 嵌入式Python环境 +│ │ ├── python.exe # Python解释器 +│ │ ├── python311.dll # Python库 +│ │ ├── Lib/ # 标准库 +│ │ ├── Scripts/ # pip等工具 +│ │ ├── python_core/ # 你的Python模块 +│ │ └── python-core.bat # 启动脚本 +│ └── tauri.conf.json # 已更新配置 +├── python_core/ # 原始Python代码 +└── scripts/ + ├── setup_embedded_python.py + └── setup_embedded_python.bat +``` + +## 🔧 工作原理 + +### 执行优先级 + +Rust代码会按以下优先级选择Python环境: + +1. **嵌入式Python** - 如果 `src-tauri/python-embed/python.exe` 存在 +2. **Sidecar** - 如果 `mixvideo-python-core.exe` 存在 +3. **系统Python** - 作为最后的回退选项 + +### 环境变量控制 + +你可以通过环境变量强制使用特定的Python: + +```cmd +# 强制使用系统Python +set MIXVIDEO_FORCE_SYSTEM_PYTHON=1 +cargo tauri dev +``` + +## 📦 构建和分发 + +### 开发模式 + +在开发模式下,嵌入式Python位于 `src-tauri/python-embed/` + +### 生产构建 + +```cmd +cargo tauri build +``` + +构建时,Tauri会自动将 `python-embed` 目录打包到最终的可执行文件中。 + +### 分发 + +最终的应用包含: +- 你的Tauri应用 +- 完整的Python环境 +- 所有Python依赖 +- 你的python_core模块 + +用户无需安装任何额外软件即可运行。 + +## 🧪 测试和调试 + +### 测试嵌入式Python + +```cmd +# 直接测试 +src-tauri\python-embed\python.exe -m python_core.main --module test --action hello + +# 测试特定服务 +src-tauri\python-embed\python.exe -m python_core.services.template_manager --action get_templates +``` + +### 在应用中测试 + +1. 启动应用:`cargo tauri dev` +2. 访问Python测试页面 +3. 运行测试 - 应该显示"Using embedded Python for execution" + +### 调试问题 + +如果遇到问题: + +1. **检查Python路径**: + ```cmd + dir src-tauri\python-embed\python.exe + ``` + +2. **检查模块导入**: + ```cmd + src-tauri\python-embed\python.exe -c "import sys; print(sys.path)" + ``` + +3. **查看日志**: + - 控制台会显示使用的Python类型 + - 检查错误信息 + +## ⚙️ 自定义配置 + +### 添加更多依赖 + +编辑 `scripts/setup_embedded_python.py`: + +```python +dependencies = [ + "requests", + "Pillow", + "numpy", # 添加新依赖 + "opencv-python", # 添加新依赖 +] +``` + +然后重新运行设置脚本。 + +### 更新Python版本 + +修改 `setup_embedded_python.py` 中的下载URL: + +```python +python_url = "https://www.python.org/ftp/python/3.12.0/python-3.12.0-embed-amd64.zip" +``` + +### 自定义Python路径 + +如果需要自定义路径,修改 `get_embedded_python_path()` 函数。 + +## 🎉 优势 + +### 对比其他方案 + +| 方案 | 优势 | 劣势 | +|------|------|------| +| **嵌入式Python** | ✅ 无需用户安装
✅ 版本一致
✅ 完全控制 | ❌ 包体积较大
❌ 更新需要重新分发 | +| Sidecar | ✅ 包体积小
✅ 独立更新 | ❌ 构建复杂
❌ 依赖管理困难 | +| 系统Python | ✅ 包体积最小 | ❌ 需要用户安装
❌ 版本不一致 | + +### 适用场景 + +嵌入式Python特别适合: +- 面向普通用户的应用 +- 需要特定Python版本的应用 +- 希望简化部署的企业应用 +- 不希望依赖外部环境的应用 + +## 🔄 维护和更新 + +### 更新Python代码 + +1. 修改 `python_core/` 中的代码 +2. 重新运行设置脚本:`scripts\setup_embedded_python.bat` +3. 测试更改:`cargo tauri dev` + +### 更新依赖 + +1. 修改 `setup_embedded_python.py` 中的依赖列表 +2. 重新运行设置脚本 +3. 测试新依赖 + +### 版本管理 + +建议在版本控制中: +- ✅ 包含 `scripts/setup_embedded_python.py` +- ✅ 包含 `python_core/` 源代码 +- ❌ 排除 `src-tauri/python-embed/` (太大) +- ✅ 在CI/CD中自动运行设置脚本 + +## 📋 最佳实践 + +1. **定期更新Python版本** - 保持安全性 +2. **最小化依赖** - 减少包体积 +3. **测试多平台** - 确保跨平台兼容性 +4. **监控包大小** - 避免过度膨胀 +5. **文档化依赖** - 便于维护 + +--- + +这个方案为你提供了一个完全自包含的Python环境,确保用户体验的一致性和简便性! diff --git a/docs/PYTHON_ENV_MANAGER_GUIDE.md b/docs/PYTHON_ENV_MANAGER_GUIDE.md new file mode 100644 index 0000000..f80f5b4 --- /dev/null +++ b/docs/PYTHON_ENV_MANAGER_GUIDE.md @@ -0,0 +1,206 @@ +# Python环境管理器使用指南 + +## 🎯 功能概述 + +Python环境管理器是一个可视化界面,用于管理你的Tauri应用中的Python环境和依赖包。它支持: + +- 📊 **环境状态监控** - 实时查看Python环境状态 +- 📦 **包管理** - 安装、卸载、升级Python包 +- 🔧 **环境设置** - 一键设置嵌入式Python环境 +- 🔍 **包搜索** - 快速查找已安装的包 + +## 🚀 快速开始 + +### 1. 访问管理页面 + +在应用中: +1. 点击左侧导航栏的 "Python环境" +2. 或直接访问 `/python-env-manager` 路由 + +### 2. 查看环境状态 + +页面会显示两种Python环境的状态: + +#### 嵌入式Python +- **优势**: 应用自带,用户无需安装 +- **状态**: 绿色表示可用,红色表示未设置 +- **设置**: 点击"设置嵌入式Python"按钮自动配置 + +#### 系统Python +- **优势**: 使用系统已安装的Python +- **状态**: 绿色表示可用,红色表示未安装 +- **要求**: 需要用户系统已安装Python + +## 📦 包管理功能 + +### 安装新包 + +1. 点击 "安装包" 按钮 +2. 输入包名(如:`numpy`, `pandas`, `opencv-python`) +3. 点击 "安装" 按钮 +4. 等待安装完成 + +**常用包推荐**: +- `requests` - HTTP请求库 +- `Pillow` - 图像处理库 +- `numpy` - 数值计算库 +- `pandas` - 数据分析库 +- `opencv-python` - 计算机视觉库 + +### 管理已安装的包 + +对于每个已安装的包,你可以: + +- **升级** - 更新到最新版本 +- **卸载** - 从环境中移除 + +### 搜索包 + +使用搜索框快速查找已安装的包: +- 输入包名进行实时搜索 +- 支持模糊匹配 + +## 🔧 环境设置 + +### 设置嵌入式Python + +如果嵌入式Python未设置(显示红色状态),可以: + +1. 点击 "设置嵌入式Python" 按钮 +2. 确认下载和安装(需要网络连接) +3. 等待自动完成设置 +4. 刷新页面查看新状态 + +**设置过程包括**: +- 下载Python 3.11嵌入式版本 +- 安装pip包管理器 +- 安装基础依赖包 +- 复制python_core模块 +- 配置环境变量 + +### 环境优先级 + +应用会按以下优先级选择Python环境: +1. **嵌入式Python** - 如果可用,优先使用 +2. **系统Python** - 作为备选方案 + +## 🛠️ 故障排除 + +### 常见问题 + +#### 1. 嵌入式Python设置失败 + +**可能原因**: +- 网络连接问题 +- 磁盘空间不足 +- 权限问题 + +**解决方案**: +- 检查网络连接 +- 确保有足够磁盘空间(至少500MB) +- 以管理员权限运行应用 + +#### 2. 包安装失败 + +**可能原因**: +- 包名错误 +- 网络问题 +- 依赖冲突 + +**解决方案**: +- 检查包名拼写 +- 确保网络连接正常 +- 查看错误信息进行诊断 + +#### 3. 系统Python不可用 + +**可能原因**: +- 系统未安装Python +- Python未添加到PATH + +**解决方案**: +- 安装Python 3.8+ +- 将Python添加到系统PATH +- 或使用嵌入式Python + +### 调试信息 + +如果遇到问题,可以: + +1. **查看控制台日志** - 开发者工具中的Console +2. **检查错误信息** - 页面会显示详细错误 +3. **重新加载状态** - 点击"刷新"按钮 + +## 📋 最佳实践 + +### 包管理建议 + +1. **最小化依赖** - 只安装必需的包 +2. **定期更新** - 保持包的最新版本 +3. **测试兼容性** - 升级后测试应用功能 +4. **备份环境** - 记录重要的包列表 + +### 环境选择建议 + +#### 选择嵌入式Python,如果: +- 希望用户无需安装Python +- 需要确保环境一致性 +- 应用面向普通用户 + +#### 选择系统Python,如果: +- 用户是开发者 +- 需要使用系统特定的包 +- 希望减少应用体积 + +## 🔄 维护和更新 + +### 定期维护 + +建议定期: +1. **检查包更新** - 升级重要的包 +2. **清理无用包** - 卸载不再需要的包 +3. **监控环境状态** - 确保Python环境正常 + +### 版本管理 + +- **记录包版本** - 在项目文档中记录关键包版本 +- **测试兼容性** - 升级前在测试环境验证 +- **回滚计划** - 准备回滚到稳定版本的方案 + +## 🎯 高级功能 + +### 环境变量控制 + +可以通过环境变量强制使用特定Python: + +```bash +# 强制使用系统Python +set MIXVIDEO_FORCE_SYSTEM_PYTHON=1 + +# 启动应用 +cargo tauri dev +``` + +### 自定义包源 + +如果需要使用自定义PyPI源,可以在设置脚本中修改: + +```python +# 在 install_dependencies 函数中添加 +subprocess.run([ + str(python_exe), "-m", "pip", "install", + "-i", "https://pypi.tuna.tsinghua.edu.cn/simple/", + dep +], ...) +``` + +## 📊 监控和统计 + +管理页面提供: +- **环境状态概览** - 一目了然的状态信息 +- **包数量统计** - 已安装包的总数 +- **实时状态更新** - 操作后自动刷新状态 + +--- + +通过Python环境管理器,你可以轻松管理应用的Python环境,确保所有功能正常运行! diff --git a/scripts/demo_python_env_manager.bat b/scripts/demo_python_env_manager.bat new file mode 100644 index 0000000..26e0afc --- /dev/null +++ b/scripts/demo_python_env_manager.bat @@ -0,0 +1,66 @@ +@echo off +REM Python环境管理器演示脚本 + +echo 🐍 Python Environment Manager Demo +echo ===================================== +echo. + +echo 这个演示将展示Python环境管理器的功能: +echo. +echo 1. 📊 查看Python环境状态 +echo 2. 📦 管理Python包 +echo 3. 🔧 设置嵌入式Python环境 +echo. + +echo 📋 使用步骤: +echo. +echo 1. 启动应用: +echo cargo tauri dev +echo. +echo 2. 访问Python环境管理页面: +echo - 点击左侧导航栏的 "Python环境" +echo - 或访问 /python-env-manager 路由 +echo. +echo 3. 查看环境状态: +echo - 绿色:环境可用 +echo - 红色:环境不可用或未设置 +echo. +echo 4. 设置嵌入式Python(如果需要): +echo - 点击 "设置嵌入式Python" 按钮 +echo - 等待自动下载和配置 +echo. +echo 5. 管理Python包: +echo - 点击 "安装包" 添加新包 +echo - 使用 "升级" 更新现有包 +echo - 使用 "卸载" 移除不需要的包 +echo. +echo 6. 搜索包: +echo - 使用搜索框快速查找已安装的包 +echo. + +echo 🎯 推荐测试包: +echo. +echo - requests (HTTP请求库) +echo - Pillow (图像处理库) +echo - certifi (SSL证书库) +echo - numpy (数值计算库,可选) +echo - pandas (数据分析库,可选) +echo. + +echo 🔧 故障排除: +echo. +echo 如果遇到问题: +echo 1. 检查网络连接 +echo 2. 确保有足够磁盘空间 +echo 3. 查看控制台错误信息 +echo 4. 点击 "刷新" 重新加载状态 +echo. + +echo ✨ 现在启动应用来体验Python环境管理器! +echo. + +pause + +echo 🚀 启动Tauri应用... +cd /d "%~dp0.." +cargo tauri dev diff --git a/scripts/setup_embedded_python.bat b/scripts/setup_embedded_python.bat new file mode 100644 index 0000000..880be59 --- /dev/null +++ b/scripts/setup_embedded_python.bat @@ -0,0 +1,40 @@ +@echo off +REM 设置嵌入式Python环境 + +echo 🐍 Setting up embedded Python for MixVideo V2... +echo. + +REM 检查Python是否可用 +python --version >nul 2>&1 +if errorlevel 1 ( + echo ❌ Python is not installed or not in PATH + echo Please install Python 3.8+ and add it to PATH + pause + exit /b 1 +) + +echo ✅ Python environment check passed + +REM 切换到项目根目录 +cd /d "%~dp0.." + +REM 运行Python设置脚本 +echo 📦 Running embedded Python setup... +python scripts\setup_embedded_python.py + +if errorlevel 1 ( + echo ❌ Setup failed + pause + exit /b 1 +) + +echo. +echo ✅ Setup completed successfully! +echo. +echo 📋 You can now: +echo 1. Test embedded Python: src-tauri\python-embed\python.exe --version +echo 2. Build your app: cargo tauri build +echo 3. The Python environment will be included in the final executable +echo. + +pause diff --git a/scripts/setup_embedded_python.py b/scripts/setup_embedded_python.py new file mode 100644 index 0000000..6918bf1 --- /dev/null +++ b/scripts/setup_embedded_python.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python3 +""" +设置嵌入式Python环境 +下载Python嵌入式版本并配置依赖 +""" + +import os +import sys +import urllib.request +import zipfile +import subprocess +import shutil +from pathlib import Path + +def get_project_root(): + """获取项目根目录""" + return Path(__file__).parent.parent + +def download_embedded_python(project_root): + """下载Python嵌入式版本""" + print("📦 Downloading Python embedded version...") + + # Python 3.11.0 嵌入式版本 + python_url = "https://www.python.org/ftp/python/3.11.0/python-3.11.0-embed-amd64.zip" + embed_dir = project_root / "src-tauri" / "python-embed" + zip_file = embed_dir / "python-embed.zip" + + # 创建目录 + embed_dir.mkdir(parents=True, exist_ok=True) + + # 下载文件 + if not zip_file.exists(): + print(f"Downloading from {python_url}...") + urllib.request.urlretrieve(python_url, zip_file) + print("✅ Download completed") + else: + print("✅ Python embed zip already exists") + + # 解压文件 + print("📂 Extracting Python...") + with zipfile.ZipFile(zip_file, 'r') as zip_ref: + zip_ref.extractall(embed_dir) + + # 删除zip文件 + zip_file.unlink() + + return embed_dir + +def setup_pip(embed_dir): + """设置pip""" + print("🔧 Setting up pip...") + + python_exe = embed_dir / "python.exe" + + # 下载get-pip.py + get_pip_url = "https://bootstrap.pypa.io/get-pip.py" + get_pip_file = embed_dir / "get-pip.py" + + if not get_pip_file.exists(): + print("Downloading get-pip.py...") + urllib.request.urlretrieve(get_pip_url, get_pip_file) + + # 修改python311._pth文件以启用site-packages + pth_file = embed_dir / "python311._pth" + if pth_file.exists(): + content = pth_file.read_text() + if "#import site" in content: + content = content.replace("#import site", "import site") + pth_file.write_text(content) + print("✅ Enabled site-packages") + + # 安装pip + try: + result = subprocess.run([ + str(python_exe), str(get_pip_file) + ], capture_output=True, text=True, cwd=embed_dir) + + if result.returncode == 0: + print("✅ pip installed successfully") + else: + print(f"❌ pip installation failed: {result.stderr}") + return False + except Exception as e: + print(f"❌ Error installing pip: {e}") + return False + + return True + +def install_dependencies(embed_dir): + """安装Python依赖""" + print("📦 Installing Python dependencies...") + + python_exe = embed_dir / "python.exe" + + # 基础依赖 + dependencies = [ + "requests", + "Pillow", + "certifi", + "urllib3", + "charset-normalizer", + "idna", + # 如果需要其他依赖,在这里添加 + # "numpy", + # "opencv-python-headless", + # "pandas", + ] + + for dep in dependencies: + print(f"Installing {dep}...") + try: + result = subprocess.run([ + str(python_exe), "-m", "pip", "install", dep + ], capture_output=True, text=True, cwd=embed_dir) + + if result.returncode == 0: + print(f"✅ {dep} installed") + else: + print(f"❌ Failed to install {dep}: {result.stderr}") + except Exception as e: + print(f"❌ Error installing {dep}: {e}") + +def copy_python_core(project_root, embed_dir): + """复制python_core模块到嵌入式Python""" + print("📋 Copying python_core module...") + + source_dir = project_root / "python_core" + target_dir = embed_dir / "python_core" + + if source_dir.exists(): + if target_dir.exists(): + shutil.rmtree(target_dir) + + shutil.copytree(source_dir, target_dir) + print("✅ python_core module copied") + else: + print("❌ python_core directory not found") + +def create_launcher_script(embed_dir): + """创建启动脚本""" + print("📝 Creating launcher script...") + + launcher_content = '''@echo off +REM Python Core Launcher Script +set PYTHON_DIR=%~dp0 +set PYTHONPATH=%PYTHON_DIR%;%PYTHON_DIR%\\python_core +"%PYTHON_DIR%\\python.exe" -m python_core.main %* +''' + + launcher_file = embed_dir / "python-core.bat" + launcher_file.write_text(launcher_content) + print("✅ Launcher script created") + +def test_embedded_python(embed_dir): + """测试嵌入式Python""" + print("🧪 Testing embedded Python...") + + python_exe = embed_dir / "python.exe" + + # 测试Python版本 + try: + result = subprocess.run([ + str(python_exe), "--version" + ], capture_output=True, text=True) + + if result.returncode == 0: + print(f"✅ Python version: {result.stdout.strip()}") + else: + print(f"❌ Python test failed: {result.stderr}") + return False + except Exception as e: + print(f"❌ Error testing Python: {e}") + return False + + # 测试依赖 + test_imports = ["requests", "PIL", "json"] + for module in test_imports: + try: + result = subprocess.run([ + str(python_exe), "-c", f"import {module}; print('{module} OK')" + ], capture_output=True, text=True) + + if result.returncode == 0: + print(f"✅ {module}: {result.stdout.strip()}") + else: + print(f"❌ {module} test failed: {result.stderr}") + except Exception as e: + print(f"❌ Error testing {module}: {e}") + + # 测试python_core模块 + try: + result = subprocess.run([ + str(python_exe), "-c", "import python_core; print('python_core OK')" + ], capture_output=True, text=True, cwd=embed_dir) + + if result.returncode == 0: + print(f"✅ python_core: {result.stdout.strip()}") + else: + print(f"❌ python_core test failed: {result.stderr}") + except Exception as e: + print(f"❌ Error testing python_core: {e}") + + return True + +def update_tauri_config(project_root, embed_dir): + """更新Tauri配置""" + print("⚙️ Updating Tauri configuration...") + + import json + + config_file = project_root / "src-tauri" / "tauri.conf.json" + + if config_file.exists(): + with open(config_file, 'r', encoding='utf-8') as f: + config = json.load(f) + + # 添加Python嵌入式目录到资源 + if 'bundle' not in config: + config['bundle'] = {} + + if 'resources' not in config['bundle']: + config['bundle']['resources'] = [] + + python_resource = "python-embed/**" + if python_resource not in config['bundle']['resources']: + config['bundle']['resources'].append(python_resource) + + # 写回配置 + with open(config_file, 'w', encoding='utf-8') as f: + json.dump(config, f, indent=2, ensure_ascii=False) + + print("✅ Tauri configuration updated") + else: + print("❌ Tauri config file not found") + +def main(): + """主函数""" + print("🚀 Setting up embedded Python for MixVideo V2...") + print("=" * 50) + + project_root = get_project_root() + print(f"Project root: {project_root}") + + try: + # 1. 下载嵌入式Python + embed_dir = download_embedded_python(project_root) + + # 2. 设置pip + if not setup_pip(embed_dir): + print("❌ Failed to setup pip") + return 1 + + # 3. 安装依赖 + install_dependencies(embed_dir) + + # 4. 复制python_core模块 + copy_python_core(project_root, embed_dir) + + # 5. 创建启动脚本 + create_launcher_script(embed_dir) + + # 6. 测试 + if not test_embedded_python(embed_dir): + print("❌ Embedded Python test failed") + return 1 + + # 7. 更新Tauri配置 + update_tauri_config(project_root, embed_dir) + + print("\n🎉 Embedded Python setup completed successfully!") + print(f"📁 Python location: {embed_dir}") + print("\n📋 Next steps:") + print("1. Test the embedded Python:") + print(f" {embed_dir}\\python.exe --version") + print("2. Build your Tauri app:") + print(" cargo tauri build") + print("3. The Python environment will be bundled with your app") + + return 0 + + except Exception as e: + print(f"❌ Setup failed: {e}") + return 1 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/test_embedded_python.bat b/scripts/test_embedded_python.bat new file mode 100644 index 0000000..a190d95 --- /dev/null +++ b/scripts/test_embedded_python.bat @@ -0,0 +1,71 @@ +@echo off +REM 测试嵌入式Python环境 + +echo 🧪 Testing Embedded Python Environment... +echo. + +REM 切换到项目根目录 +cd /d "%~dp0.." + +REM 检查嵌入式Python是否存在 +if not exist "src-tauri\python-embed\python.exe" ( + echo ❌ Embedded Python not found + echo Please run: scripts\setup_embedded_python.bat + pause + exit /b 1 +) + +echo ✅ Embedded Python found + +REM 测试Python版本 +echo 📋 Testing Python version... +src-tauri\python-embed\python.exe --version +if errorlevel 1 ( + echo ❌ Python version test failed + pause + exit /b 1 +) + +REM 测试基础依赖 +echo 📦 Testing dependencies... +src-tauri\python-embed\python.exe -c "import requests; print('✅ requests OK')" +src-tauri\python-embed\python.exe -c "import PIL; print('✅ Pillow OK')" +src-tauri\python-embed\python.exe -c "import json; print('✅ json OK')" + +REM 测试python_core模块 +echo 🔧 Testing python_core module... +src-tauri\python-embed\python.exe -c "import python_core; print('✅ python_core OK')" +if errorlevel 1 ( + echo ❌ python_core module test failed + echo The module might not be copied correctly + pause + exit /b 1 +) + +REM 测试主入口 +echo 🚀 Testing main entry point... +src-tauri\python-embed\python.exe -m python_core.main --module test --action hello +if errorlevel 1 ( + echo ❌ Main entry point test failed + pause + exit /b 1 +) + +REM 测试服务模块 +echo 🔧 Testing service modules... +src-tauri\python-embed\python.exe -m python_core.main --module template_manager --action get_templates +if errorlevel 1 ( + echo ❌ Service module test failed + echo This is expected if using mock services +) + +echo. +echo 🎉 All tests passed! +echo. +echo 📋 Next steps: +echo 1. Start your Tauri app: cargo tauri dev +echo 2. Go to Python Test page +echo 3. Run tests - should show "Using embedded Python for execution" +echo. + +pause diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 8b8d2a2..27d581c 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -11,6 +11,7 @@ pub mod audio; pub mod media; pub mod resource_category; pub mod python_core_demo; +pub mod python_env_manager; // Re-export all commands for easy access pub use basic::*; diff --git a/src-tauri/src/commands/python_env_manager.rs b/src-tauri/src/commands/python_env_manager.rs new file mode 100644 index 0000000..f18baeb --- /dev/null +++ b/src-tauri/src/commands/python_env_manager.rs @@ -0,0 +1,295 @@ +/** + * Python环境管理命令 + * 用于管理Python环境和依赖 + */ + +use serde::{Deserialize, Serialize}; +use tauri::{command, AppHandle}; +use std::process::Command; +use std::path::PathBuf; + +#[derive(Debug, Serialize, Deserialize)] +pub struct PythonEnvInfo { + pub python_type: String, + pub python_path: Option, + pub version: Option, + pub is_available: bool, + pub error: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PythonPackage { + pub name: String, + pub version: String, + pub location: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PythonEnvStatus { + pub embedded: PythonEnvInfo, + pub system: PythonEnvInfo, + pub current_env: String, + pub packages: Vec, +} + +/// 获取嵌入式Python路径 +fn get_embedded_python_path() -> Option { + let python_exe = if cfg!(target_os = "windows") { + "python.exe" + } else { + "python3" + }; + + let possible_paths = vec![ + std::env::current_dir().ok() + .map(|p| p.join("src-tauri").join("python-embed").join(python_exe)), + std::env::current_exe().ok() + .and_then(|p| p.parent().map(|p| p.join("python-embed").join(python_exe))), + ]; + + for path in possible_paths.into_iter().flatten() { + if path.exists() { + return Some(path); + } + } + + None +} + +/// 检查Python环境信息 +fn check_python_env(python_path: &str) -> PythonEnvInfo { + let mut cmd = Command::new(python_path); + cmd.arg("--version"); + + match cmd.output() { + Ok(output) => { + if output.status.success() { + let version = String::from_utf8_lossy(&output.stdout).trim().to_string(); + PythonEnvInfo { + python_type: if python_path.contains("python-embed") { "embedded".to_string() } else { "system".to_string() }, + python_path: Some(python_path.to_string()), + version: Some(version), + is_available: true, + error: None, + } + } else { + let error = String::from_utf8_lossy(&output.stderr).trim().to_string(); + PythonEnvInfo { + python_type: if python_path.contains("python-embed") { "embedded".to_string() } else { "system".to_string() }, + python_path: Some(python_path.to_string()), + version: None, + is_available: false, + error: Some(error), + } + } + } + Err(e) => PythonEnvInfo { + python_type: if python_path.contains("python-embed") { "embedded".to_string() } else { "system".to_string() }, + python_path: Some(python_path.to_string()), + version: None, + is_available: false, + error: Some(e.to_string()), + } + } +} + +/// 获取已安装的Python包列表 +fn get_installed_packages(python_path: &str) -> Vec { + let mut cmd = Command::new(python_path); + cmd.args(&["-m", "pip", "list", "--format=json"]); + + match cmd.output() { + Ok(output) => { + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + match serde_json::from_str::>(&stdout) { + Ok(packages) => { + packages.into_iter().filter_map(|pkg| { + let name = pkg.get("name")?.as_str()?.to_string(); + let version = pkg.get("version")?.as_str()?.to_string(); + Some(PythonPackage { + name, + version, + location: None, + }) + }).collect() + } + Err(_) => Vec::new(), + } + } else { + Vec::new() + } + } + Err(_) => Vec::new(), + } +} + +/// 获取Python环境状态 +#[command] +pub async fn get_python_env_status(_app: AppHandle) -> Result { + println!("Getting Python environment status..."); + + // 检查嵌入式Python + let embedded_info = if let Some(embedded_path) = get_embedded_python_path() { + check_python_env(&embedded_path.to_string_lossy()) + } else { + PythonEnvInfo { + python_type: "embedded".to_string(), + python_path: None, + version: None, + is_available: false, + error: Some("Embedded Python not found".to_string()), + } + }; + + // 检查系统Python + let system_info = check_python_env("python"); + + // 确定当前使用的环境 + let current_env = if embedded_info.is_available { + "embedded".to_string() + } else if system_info.is_available { + "system".to_string() + } else { + "none".to_string() + }; + + // 获取当前环境的包列表 + let packages = if embedded_info.is_available { + if let Some(ref path) = embedded_info.python_path { + get_installed_packages(path) + } else { + Vec::new() + } + } else if system_info.is_available { + get_installed_packages("python") + } else { + Vec::new() + }; + + Ok(PythonEnvStatus { + embedded: embedded_info, + system: system_info, + current_env, + packages, + }) +} + +/// 安装Python包 +#[command] +pub async fn install_python_package(_app: AppHandle, package_name: String) -> Result { + println!("Installing Python package: {}", package_name); + + // 确定使用哪个Python + let python_path = if let Some(embedded_path) = get_embedded_python_path() { + embedded_path.to_string_lossy().to_string() + } else { + "python".to_string() + }; + + let mut cmd = Command::new(&python_path); + cmd.args(&["-m", "pip", "install", &package_name]); + + match cmd.output() { + Ok(output) => { + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + Ok(format!("Successfully installed {}: {}", package_name, stdout)) + } else { + let stderr = String::from_utf8_lossy(&output.stderr); + Err(format!("Failed to install {}: {}", package_name, stderr)) + } + } + Err(e) => Err(format!("Failed to execute pip install: {}", e)) + } +} + +/// 卸载Python包 +#[command] +pub async fn uninstall_python_package(_app: AppHandle, package_name: String) -> Result { + println!("Uninstalling Python package: {}", package_name); + + // 确定使用哪个Python + let python_path = if let Some(embedded_path) = get_embedded_python_path() { + embedded_path.to_string_lossy().to_string() + } else { + "python".to_string() + }; + + let mut cmd = Command::new(&python_path); + cmd.args(&["-m", "pip", "uninstall", "-y", &package_name]); + + match cmd.output() { + Ok(output) => { + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + Ok(format!("Successfully uninstalled {}: {}", package_name, stdout)) + } else { + let stderr = String::from_utf8_lossy(&output.stderr); + Err(format!("Failed to uninstall {}: {}", package_name, stderr)) + } + } + Err(e) => Err(format!("Failed to execute pip uninstall: {}", e)) + } +} + +/// 升级Python包 +#[command] +pub async fn upgrade_python_package(_app: AppHandle, package_name: String) -> Result { + println!("Upgrading Python package: {}", package_name); + + // 确定使用哪个Python + let python_path = if let Some(embedded_path) = get_embedded_python_path() { + embedded_path.to_string_lossy().to_string() + } else { + "python".to_string() + }; + + let mut cmd = Command::new(&python_path); + cmd.args(&["-m", "pip", "install", "--upgrade", &package_name]); + + match cmd.output() { + Ok(output) => { + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + Ok(format!("Successfully upgraded {}: {}", package_name, stdout)) + } else { + let stderr = String::from_utf8_lossy(&output.stderr); + Err(format!("Failed to upgrade {}: {}", package_name, stderr)) + } + } + Err(e) => Err(format!("Failed to execute pip upgrade: {}", e)) + } +} + +/// 设置嵌入式Python环境 +#[command] +pub async fn setup_embedded_python(_app: AppHandle) -> Result { + println!("Setting up embedded Python environment..."); + + // 运行设置脚本 + let script_path = std::env::current_dir() + .map_err(|e| format!("Failed to get current directory: {}", e))? + .join("scripts") + .join("setup_embedded_python.py"); + + if !script_path.exists() { + return Err("Setup script not found. Please ensure scripts/setup_embedded_python.py exists.".to_string()); + } + + let mut cmd = Command::new("python"); + cmd.arg(&script_path); + + match cmd.output() { + Ok(output) => { + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + Ok(format!("Embedded Python setup completed: {}", stdout)) + } else { + let stderr = String::from_utf8_lossy(&output.stderr); + Err(format!("Failed to setup embedded Python: {}", stderr)) + } + } + Err(e) => Err(format!("Failed to execute setup script: {}", e)) + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 1be6b32..8c94733 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -100,7 +100,13 @@ pub fn run() { commands::python_core_demo::test_batch_import_templates, commands::python_core_demo::test_create_project, commands::python_core_demo::test_get_categories, - commands::python_core_demo::test_all_python_core_functions + commands::python_core_demo::test_all_python_core_functions, + // Python Environment Manager Commands + commands::python_env_manager::get_python_env_status, + commands::python_env_manager::install_python_package, + commands::python_env_manager::uninstall_python_package, + commands::python_env_manager::upgrade_python_package, + commands::python_env_manager::setup_embedded_python ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/python_executor.rs b/src-tauri/src/python_executor.rs index 4f6827d..53d9a3e 100644 --- a/src-tauri/src/python_executor.rs +++ b/src-tauri/src/python_executor.rs @@ -67,6 +67,104 @@ fn get_sidecar_path() -> Option { None } +/// Get the path to embedded Python executable +fn get_embedded_python_path() -> Option { + let python_exe = if cfg!(target_os = "windows") { + "python.exe" + } else { + "python3" + }; + + // 检查可能的嵌入式Python路径 + let possible_paths = vec![ + // 开发环境:src-tauri/python-embed/ + std::env::current_dir().ok() + .map(|p| p.join("src-tauri").join("python-embed").join(python_exe)), + // 生产环境:与主程序同目录的python-embed/ + std::env::current_exe().ok() + .and_then(|p| p.parent().map(|p| p.join("python-embed").join(python_exe))), + // 相对路径 + Some(std::path::PathBuf::from("python-embed").join(python_exe)), + ]; + + for path in possible_paths.into_iter().flatten() { + if path.exists() { + println!("Found embedded Python at: {:?}", path); + return Some(path); + } + } + + None +} + +/// Check if we should use embedded Python +fn should_use_embedded_python() -> bool { + // 检查环境变量强制设置 + if let Ok(force_system) = std::env::var("MIXVIDEO_FORCE_SYSTEM_PYTHON") { + if force_system == "1" || force_system.to_lowercase() == "true" { + println!("Forced to use system Python by environment variable"); + return false; + } + } + + // 检查嵌入式Python是否存在 + if get_embedded_python_path().is_some() { + return true; + } + + println!("No embedded Python found, will use system Python"); + false +} + +/// Execute Python command using embedded Python +async fn execute_python_embedded( + module: &str, + action: &str, + params: Option<&str>, + config: Option, +) -> Result { + let _config = config.unwrap_or_default(); + + println!("Executing Python with embedded Python: module={}, action={}", module, action); + + // 获取嵌入式Python路径 + let python_path = get_embedded_python_path() + .ok_or_else(|| "Embedded Python executable not found".to_string())?; + + // 构建命令 + let mut cmd = Command::new(&python_path); + + // 设置环境变量 + cmd.env("PYTHONPATH", python_path.parent().unwrap()); + + // 构建参数 + cmd.args(&["-m", &format!("python_core.services.{}", module)]) + .args(&["--action", action]); + + if let Some(params_str) = params { + cmd.args(&["--params", params_str]); + } + + // 配置命令(隐藏控制台窗口等) + crate::command_utils::configure_no_window(&mut cmd); + + cmd.stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + + // 执行命令 + let output = cmd.output() + .map_err(|e| format!("Failed to execute embedded Python: {}", e))?; + + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + println!("Embedded Python output: {}", stdout); + Ok(stdout.to_string()) + } else { + let stderr = String::from_utf8_lossy(&output.stderr); + Err(format!("Embedded Python failed: {}", stderr)) + } +} + /// Execute Python command using sidecar async fn execute_python_sidecar( module: &str, @@ -250,7 +348,7 @@ pub async fn execute_python_with_events( execute_python_command_with_progress(app, args, config, Some(progress_callback)).await } -/// Execute Python module action with automatic sidecar/system Python selection +/// Execute Python module action with automatic embedded/sidecar/system Python selection pub async fn execute_python_module_action( app: tauri::AppHandle, module: &str, @@ -258,7 +356,24 @@ pub async fn execute_python_module_action( params: Option, config: Option, ) -> Result { - // 检查是否应该使用sidecar + // 优先级:嵌入式Python > Sidecar > 系统Python + + // 1. 检查是否应该使用嵌入式Python + if should_use_embedded_python() { + println!("Using embedded Python for execution"); + + // 将参数转换为JSON字符串 + let params_str = if let Some(p) = params { + Some(serde_json::to_string(&p) + .map_err(|e| format!("Failed to serialize parameters: {}", e))?) + } else { + None + }; + + return execute_python_embedded(module, action, params_str.as_deref(), config).await; + } + + // 2. 检查是否应该使用sidecar if should_use_sidecar() { println!("Using Python sidecar for execution"); @@ -270,27 +385,28 @@ pub async fn execute_python_module_action( None }; - execute_python_sidecar(module, action, params_str.as_deref(), config).await - } else { - println!("Using system Python for execution"); - - // 构建传统的命令行参数 - let mut args = vec![ - "-m".to_string(), - format!("python_core.{}", module), - "--action".to_string(), - action.to_string(), - ]; - - // 添加参数 - if let Some(p) = params { - args.push("--params".to_string()); - args.push(serde_json::to_string(&p) - .map_err(|e| format!("Failed to serialize parameters: {}", e))?); - } - - execute_python_command(app, &args, config).await + return execute_python_sidecar(module, action, params_str.as_deref(), config).await; } + + // 3. 使用系统Python + println!("Using system Python for execution"); + + // 构建传统的命令行参数 + let mut args = vec![ + "-m".to_string(), + format!("python_core.{}", module), + "--action".to_string(), + action.to_string(), + ]; + + // 添加参数 + if let Some(p) = params { + args.push("--params".to_string()); + args.push(serde_json::to_string(&p) + .map_err(|e| format!("Failed to serialize parameters: {}", e))?); + } + + execute_python_command(app, &args, config).await } /// Execute Python module action with progress events diff --git a/src/App.tsx b/src/App.tsx index 9687b88..fc7b8f0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,6 +16,7 @@ import MediaLibraryPage from './pages/MediaLibraryPage' import KVTestPage from './pages/KVTestPage' import TextVideoGeneratorPage from './pages/TextVideoGeneratorPage' import PythonCoreTestPage from './pages/PythonCoreTestPage' +import PythonEnvManagerPage from './pages/PythonEnvManagerPage' function App() { return ( @@ -31,6 +32,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index d4e4a82..1175e6c 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react' import { Link, useLocation } from 'react-router-dom' -import { Home, Video, Settings, FolderOpen, Music, Image, Sparkles, Layout, Clock, CheckCircle, XCircle, List, Tags, User, Wand2, TestTube } from 'lucide-react' +import { Home, Video, Settings, FolderOpen, Music, Image, Sparkles, Layout, Clock, CheckCircle, XCircle, List, Tags, User, Wand2, TestTube, Package } from 'lucide-react' import { clsx } from 'clsx' const Sidebar: React.FC = () => { @@ -30,6 +30,7 @@ const Sidebar: React.FC = () => { { path: '/media', icon: Image, label: '媒体库' }, { path: '/audio', icon: Music, label: '音频库' }, { path: '/python-core-test', icon: TestTube, label: 'Python测试' }, + { path: '/python-env-manager', icon: Package, label: 'Python环境' }, { path: '/settings', icon: Settings, label: '设置' }, ] diff --git a/src/pages/PythonEnvManagerPage.tsx b/src/pages/PythonEnvManagerPage.tsx new file mode 100644 index 0000000..b45e7df --- /dev/null +++ b/src/pages/PythonEnvManagerPage.tsx @@ -0,0 +1,400 @@ +/** + * Python环境管理页面 + * 用于管理Python环境和依赖包 + */ + +import React, { useState, useEffect } from 'react' +import { invoke } from '@tauri-apps/api/core' +import { + Python, + Package, + Download, + Trash2, + RefreshCw, + Plus, + Settings, + CheckCircle, + XCircle, + AlertCircle, + Search, + Loader +} from 'lucide-react' + +interface PythonEnvInfo { + python_type: string + python_path?: string + version?: string + is_available: boolean + error?: string +} + +interface PythonPackage { + name: string + version: string + location?: string +} + +interface PythonEnvStatus { + embedded: PythonEnvInfo + system: PythonEnvInfo + current_env: string + packages: PythonPackage[] +} + +const PythonEnvManagerPage: React.FC = () => { + const [envStatus, setEnvStatus] = useState(null) + const [loading, setLoading] = useState(true) + const [installing, setInstalling] = useState(null) + const [newPackageName, setNewPackageName] = useState('') + const [searchTerm, setSearchTerm] = useState('') + const [showInstallForm, setShowInstallForm] = useState(false) + + // 加载Python环境状态 + const loadEnvStatus = async () => { + try { + setLoading(true) + const status: PythonEnvStatus = await invoke('get_python_env_status') + setEnvStatus(status) + } catch (error) { + console.error('Failed to load Python environment status:', error) + } finally { + setLoading(false) + } + } + + useEffect(() => { + loadEnvStatus() + }, []) + + // 安装包 + const handleInstallPackage = async (packageName: string) => { + if (!packageName.trim()) return + + try { + setInstalling(packageName) + await invoke('install_python_package', { packageName: packageName.trim() }) + await loadEnvStatus() // 重新加载状态 + setNewPackageName('') + setShowInstallForm(false) + } catch (error) { + console.error('Failed to install package:', error) + alert(`安装失败: ${error}`) + } finally { + setInstalling(null) + } + } + + // 卸载包 + const handleUninstallPackage = async (packageName: string) => { + if (!confirm(`确定要卸载 ${packageName} 吗?`)) return + + try { + setInstalling(packageName) + await invoke('uninstall_python_package', { packageName }) + await loadEnvStatus() // 重新加载状态 + } catch (error) { + console.error('Failed to uninstall package:', error) + alert(`卸载失败: ${error}`) + } finally { + setInstalling(null) + } + } + + // 升级包 + const handleUpgradePackage = async (packageName: string) => { + try { + setInstalling(packageName) + await invoke('upgrade_python_package', { packageName }) + await loadEnvStatus() // 重新加载状态 + } catch (error) { + console.error('Failed to upgrade package:', error) + alert(`升级失败: ${error}`) + } finally { + setInstalling(null) + } + } + + // 设置嵌入式Python + const handleSetupEmbeddedPython = async () => { + if (!confirm('这将下载并设置嵌入式Python环境,可能需要几分钟时间。继续吗?')) return + + try { + setLoading(true) + await invoke('setup_embedded_python') + await loadEnvStatus() // 重新加载状态 + alert('嵌入式Python环境设置完成!') + } catch (error) { + console.error('Failed to setup embedded Python:', error) + alert(`设置失败: ${error}`) + } finally { + setLoading(false) + } + } + + // 过滤包列表 + const filteredPackages = envStatus?.packages.filter(pkg => + pkg.name.toLowerCase().includes(searchTerm.toLowerCase()) + ) || [] + + const getEnvStatusIcon = (env: PythonEnvInfo) => { + if (env.is_available) { + return + } else { + return + } + } + + const getEnvStatusColor = (env: PythonEnvInfo) => { + if (env.is_available) { + return 'border-green-200 bg-green-50' + } else { + return 'border-red-200 bg-red-50' + } + } + + if (loading && !envStatus) { + return ( +
+
+ +

正在加载Python环境信息...

+
+
+ ) + } + + return ( +
+
+ {/* 页面标题 */} +
+

+ + Python环境管理 +

+

+ 管理Python环境和依赖包,确保应用正常运行 +

+
+ + {/* Python环境状态 */} +
+ {/* 嵌入式Python */} +
+
+
+ {getEnvStatusIcon(envStatus?.embedded || {} as PythonEnvInfo)} +
+

嵌入式Python

+

应用内置Python环境

+
+
+ {envStatus?.current_env === 'embedded' && ( + + 当前使用 + + )} +
+ + {envStatus?.embedded.is_available ? ( +
+
版本: {envStatus.embedded.version}
+
路径: {envStatus.embedded.python_path}
+
+ ) : ( +
+
+ 错误: {envStatus?.embedded.error} +
+ +
+ )} +
+ + {/* 系统Python */} +
+
+
+ {getEnvStatusIcon(envStatus?.system || {} as PythonEnvInfo)} +
+

系统Python

+

系统安装的Python

+
+
+ {envStatus?.current_env === 'system' && ( + + 当前使用 + + )} +
+ + {envStatus?.system.is_available ? ( +
+
版本: {envStatus.system.version}
+
路径: {envStatus.system.python_path}
+
+ ) : ( +
+ 错误: {envStatus?.system.error} +
+ )} +
+
+ + {/* 包管理 */} +
+
+
+ +
+

已安装的包

+

+ 当前环境: {envStatus?.current_env === 'embedded' ? '嵌入式Python' : + envStatus?.current_env === 'system' ? '系统Python' : '无可用环境'} +

+
+
+ +
+ + + +
+
+ + {/* 安装包表单 */} + {showInstallForm && ( +
+
+ setNewPackageName(e.target.value)} + placeholder="输入包名,如: requests, numpy, pillow" + className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" + onKeyPress={(e) => e.key === 'Enter' && handleInstallPackage(newPackageName)} + /> + + +
+
+ )} + + {/* 搜索框 */} +
+
+ + setSearchTerm(e.target.value)} + placeholder="搜索已安装的包..." + className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+
+ + {/* 包列表 */} +
+ {filteredPackages.length === 0 ? ( +
+ {envStatus?.packages.length === 0 ? '没有找到已安装的包' : '没有匹配的包'} +
+ ) : ( + filteredPackages.map((pkg) => ( +
+
+ +
+
{pkg.name}
+
版本: {pkg.version}
+
+
+ +
+ + + +
+
+ )) + )} +
+
+ + {/* 环境信息 */} + {envStatus?.current_env === 'none' && ( +
+
+ +
+

没有可用的Python环境

+

+ 请设置嵌入式Python环境或确保系统已安装Python。 +

+
+
+
+ )} +
+
+ ) +} + +export default PythonEnvManagerPage