This commit is contained in:
root
2025-07-11 15:14:52 +08:00
parent fb3cf1f62f
commit 6671f590e9
13 changed files with 1738 additions and 24 deletions

View File

@@ -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** | ✅ 无需用户安装<br>✅ 版本一致<br>✅ 完全控制 | ❌ 包体积较大<br>❌ 更新需要重新分发 |
| Sidecar | ✅ 包体积小<br>✅ 独立更新 | ❌ 构建复杂<br>❌ 依赖管理困难 |
| 系统Python | ✅ 包体积最小 | ❌ 需要用户安装<br>❌ 版本不一致 |
### 适用场景
嵌入式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环境确保用户体验的一致性和简便性

View File

@@ -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环境确保所有功能正常运行

View File

@@ -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

View File

@@ -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

View File

@@ -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())

View File

@@ -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

View File

@@ -11,6 +11,7 @@ pub mod audio;
pub mod media; pub mod media;
pub mod resource_category; pub mod resource_category;
pub mod python_core_demo; pub mod python_core_demo;
pub mod python_env_manager;
// Re-export all commands for easy access // Re-export all commands for easy access
pub use basic::*; pub use basic::*;

View File

@@ -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<String>,
pub version: Option<String>,
pub is_available: bool,
pub error: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PythonPackage {
pub name: String,
pub version: String,
pub location: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PythonEnvStatus {
pub embedded: PythonEnvInfo,
pub system: PythonEnvInfo,
pub current_env: String,
pub packages: Vec<PythonPackage>,
}
/// 获取嵌入式Python路径
fn get_embedded_python_path() -> Option<PathBuf> {
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<PythonPackage> {
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::<Vec<serde_json::Value>>(&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<PythonEnvStatus, String> {
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<String, String> {
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<String, String> {
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<String, String> {
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<String, String> {
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))
}
}

View File

@@ -100,7 +100,13 @@ pub fn run() {
commands::python_core_demo::test_batch_import_templates, commands::python_core_demo::test_batch_import_templates,
commands::python_core_demo::test_create_project, commands::python_core_demo::test_create_project,
commands::python_core_demo::test_get_categories, 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!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

View File

@@ -67,6 +67,104 @@ fn get_sidecar_path() -> Option<std::path::PathBuf> {
None None
} }
/// Get the path to embedded Python executable
fn get_embedded_python_path() -> Option<std::path::PathBuf> {
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<PythonExecutorConfig>,
) -> Result<String, String> {
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 /// Execute Python command using sidecar
async fn execute_python_sidecar( async fn execute_python_sidecar(
module: &str, 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_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( pub async fn execute_python_module_action(
app: tauri::AppHandle, app: tauri::AppHandle,
module: &str, module: &str,
@@ -258,7 +356,24 @@ pub async fn execute_python_module_action(
params: Option<serde_json::Value>, params: Option<serde_json::Value>,
config: Option<PythonExecutorConfig>, config: Option<PythonExecutorConfig>,
) -> Result<String, String> { ) -> Result<String, String> {
// 检查是否应该使用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() { if should_use_sidecar() {
println!("Using Python sidecar for execution"); println!("Using Python sidecar for execution");
@@ -270,8 +385,10 @@ pub async fn execute_python_module_action(
None None
}; };
execute_python_sidecar(module, action, params_str.as_deref(), config).await return execute_python_sidecar(module, action, params_str.as_deref(), config).await;
} else { }
// 3. 使用系统Python
println!("Using system Python for execution"); println!("Using system Python for execution");
// 构建传统的命令行参数 // 构建传统的命令行参数
@@ -290,7 +407,6 @@ pub async fn execute_python_module_action(
} }
execute_python_command(app, &args, config).await execute_python_command(app, &args, config).await
}
} }
/// Execute Python module action with progress events /// Execute Python module action with progress events

View File

@@ -16,6 +16,7 @@ import MediaLibraryPage from './pages/MediaLibraryPage'
import KVTestPage from './pages/KVTestPage' import KVTestPage from './pages/KVTestPage'
import TextVideoGeneratorPage from './pages/TextVideoGeneratorPage' import TextVideoGeneratorPage from './pages/TextVideoGeneratorPage'
import PythonCoreTestPage from './pages/PythonCoreTestPage' import PythonCoreTestPage from './pages/PythonCoreTestPage'
import PythonEnvManagerPage from './pages/PythonEnvManagerPage'
function App() { function App() {
return ( return (
<Routes> <Routes>
@@ -31,6 +32,7 @@ function App() {
<Route path="/ai-video" element={<AIVideoPage />} /> <Route path="/ai-video" element={<AIVideoPage />} />
<Route path="/text-video-generator" element={<TextVideoGeneratorPage />} /> <Route path="/text-video-generator" element={<TextVideoGeneratorPage />} />
<Route path="/python-core-test" element={<PythonCoreTestPage />} /> <Route path="/python-core-test" element={<PythonCoreTestPage />} />
<Route path="/python-env-manager" element={<PythonEnvManagerPage />} />
<Route path="/templates" element={<TemplateManagePage />} /> <Route path="/templates" element={<TemplateManagePage />} />
<Route path="/templates/:templateId" element={<TemplateDetailPage />} /> <Route path="/templates/:templateId" element={<TemplateDetailPage />} />
<Route path="/resource-categories" element={<ResourceCategoryPage />} /> <Route path="/resource-categories" element={<ResourceCategoryPage />} />

View File

@@ -1,6 +1,6 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { Link, useLocation } from 'react-router-dom' 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' import { clsx } from 'clsx'
const Sidebar: React.FC = () => { const Sidebar: React.FC = () => {
@@ -30,6 +30,7 @@ const Sidebar: React.FC = () => {
{ path: '/media', icon: Image, label: '媒体库' }, { path: '/media', icon: Image, label: '媒体库' },
{ path: '/audio', icon: Music, label: '音频库' }, { path: '/audio', icon: Music, label: '音频库' },
{ path: '/python-core-test', icon: TestTube, label: 'Python测试' }, { path: '/python-core-test', icon: TestTube, label: 'Python测试' },
{ path: '/python-env-manager', icon: Package, label: 'Python环境' },
{ path: '/settings', icon: Settings, label: '设置' }, { path: '/settings', icon: Settings, label: '设置' },
] ]

View File

@@ -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<PythonEnvStatus | null>(null)
const [loading, setLoading] = useState(true)
const [installing, setInstalling] = useState<string | null>(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 <CheckCircle className="w-5 h-5 text-green-500" />
} else {
return <XCircle className="w-5 h-5 text-red-500" />
}
}
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 (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<Loader className="w-8 h-8 animate-spin mx-auto mb-4 text-blue-500" />
<p className="text-gray-600">Python环境信息...</p>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-gray-50 p-6">
<div className="max-w-6xl mx-auto">
{/* 页面标题 */}
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-2 flex items-center gap-3">
<Python className="w-8 h-8 text-blue-600" />
Python环境管理
</h1>
<p className="text-gray-600">
Python环境和依赖包
</p>
</div>
{/* Python环境状态 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
{/* 嵌入式Python */}
<div className={`bg-white rounded-lg shadow-sm border-2 p-6 ${getEnvStatusColor(envStatus?.embedded || {} as PythonEnvInfo)}`}>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
{getEnvStatusIcon(envStatus?.embedded || {} as PythonEnvInfo)}
<div>
<h3 className="font-semibold text-gray-900">Python</h3>
<p className="text-sm text-gray-600">Python环境</p>
</div>
</div>
{envStatus?.current_env === 'embedded' && (
<span className="px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded-full">
使
</span>
)}
</div>
{envStatus?.embedded.is_available ? (
<div className="space-y-2 text-sm">
<div><strong>:</strong> {envStatus.embedded.version}</div>
<div><strong>:</strong> {envStatus.embedded.python_path}</div>
</div>
) : (
<div className="space-y-2">
<div className="text-sm text-red-600">
<strong>:</strong> {envStatus?.embedded.error}
</div>
<button
onClick={handleSetupEmbeddedPython}
disabled={loading}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
<Download size={16} />
Python
</button>
</div>
)}
</div>
{/* 系统Python */}
<div className={`bg-white rounded-lg shadow-sm border-2 p-6 ${getEnvStatusColor(envStatus?.system || {} as PythonEnvInfo)}`}>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
{getEnvStatusIcon(envStatus?.system || {} as PythonEnvInfo)}
<div>
<h3 className="font-semibold text-gray-900">Python</h3>
<p className="text-sm text-gray-600">Python</p>
</div>
</div>
{envStatus?.current_env === 'system' && (
<span className="px-2 py-1 bg-green-100 text-green-800 text-xs rounded-full">
使
</span>
)}
</div>
{envStatus?.system.is_available ? (
<div className="space-y-2 text-sm">
<div><strong>:</strong> {envStatus.system.version}</div>
<div><strong>:</strong> {envStatus.system.python_path}</div>
</div>
) : (
<div className="text-sm text-red-600">
<strong>:</strong> {envStatus?.system.error}
</div>
)}
</div>
</div>
{/* 包管理 */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<Package className="w-6 h-6 text-green-600" />
<div>
<h2 className="text-xl font-semibold text-gray-900"></h2>
<p className="text-sm text-gray-600">
: {envStatus?.current_env === 'embedded' ? '嵌入式Python' :
envStatus?.current_env === 'system' ? '系统Python' : '无可用环境'}
</p>
</div>
</div>
<div className="flex items-center gap-3">
<button
onClick={loadEnvStatus}
disabled={loading}
className="flex items-center gap-2 px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 disabled:opacity-50"
>
<RefreshCw size={16} className={loading ? 'animate-spin' : ''} />
</button>
<button
onClick={() => setShowInstallForm(!showInstallForm)}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
<Plus size={16} />
</button>
</div>
</div>
{/* 安装包表单 */}
{showInstallForm && (
<div className="mb-6 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<div className="flex items-center gap-3">
<input
type="text"
value={newPackageName}
onChange={(e) => 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)}
/>
<button
onClick={() => handleInstallPackage(newPackageName)}
disabled={!newPackageName.trim() || installing !== null}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
</button>
<button
onClick={() => setShowInstallForm(false)}
className="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700"
>
</button>
</div>
</div>
)}
{/* 搜索框 */}
<div className="mb-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<input
type="text"
value={searchTerm}
onChange={(e) => 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"
/>
</div>
</div>
{/* 包列表 */}
<div className="space-y-2">
{filteredPackages.length === 0 ? (
<div className="text-center py-8 text-gray-500">
{envStatus?.packages.length === 0 ? '没有找到已安装的包' : '没有匹配的包'}
</div>
) : (
filteredPackages.map((pkg) => (
<div
key={pkg.name}
className="flex items-center justify-between p-4 border border-gray-200 rounded-lg hover:bg-gray-50"
>
<div className="flex items-center gap-3">
<Package className="w-5 h-5 text-gray-400" />
<div>
<div className="font-medium text-gray-900">{pkg.name}</div>
<div className="text-sm text-gray-500">: {pkg.version}</div>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => handleUpgradePackage(pkg.name)}
disabled={installing === pkg.name}
className="flex items-center gap-1 px-3 py-1 text-sm bg-green-100 text-green-700 rounded hover:bg-green-200 disabled:opacity-50"
>
{installing === pkg.name ? (
<Loader size={14} className="animate-spin" />
) : (
<RefreshCw size={14} />
)}
</button>
<button
onClick={() => handleUninstallPackage(pkg.name)}
disabled={installing === pkg.name}
className="flex items-center gap-1 px-3 py-1 text-sm bg-red-100 text-red-700 rounded hover:bg-red-200 disabled:opacity-50"
>
{installing === pkg.name ? (
<Loader size={14} className="animate-spin" />
) : (
<Trash2 size={14} />
)}
</button>
</div>
</div>
))
)}
</div>
</div>
{/* 环境信息 */}
{envStatus?.current_env === 'none' && (
<div className="mt-6 bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<div className="flex items-center gap-3">
<AlertCircle className="w-5 h-5 text-yellow-600" />
<div>
<h3 className="font-medium text-yellow-800">Python环境</h3>
<p className="text-sm text-yellow-700 mt-1">
Python环境或确保系统已安装Python
</p>
</div>
</div>
</div>
)}
</div>
</div>
)
}
export default PythonEnvManagerPage