fix
This commit is contained in:
224
docs/EMBEDDED_PYTHON_GUIDE.md
Normal file
224
docs/EMBEDDED_PYTHON_GUIDE.md
Normal 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环境,确保用户体验的一致性和简便性!
|
||||
206
docs/PYTHON_ENV_MANAGER_GUIDE.md
Normal file
206
docs/PYTHON_ENV_MANAGER_GUIDE.md
Normal 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环境,确保所有功能正常运行!
|
||||
66
scripts/demo_python_env_manager.bat
Normal file
66
scripts/demo_python_env_manager.bat
Normal 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
|
||||
40
scripts/setup_embedded_python.bat
Normal file
40
scripts/setup_embedded_python.bat
Normal 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
|
||||
286
scripts/setup_embedded_python.py
Normal file
286
scripts/setup_embedded_python.py
Normal 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())
|
||||
71
scripts/test_embedded_python.bat
Normal file
71
scripts/test_embedded_python.bat
Normal 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
|
||||
@@ -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::*;
|
||||
|
||||
295
src-tauri/src/commands/python_env_manager.rs
Normal file
295
src-tauri/src/commands/python_env_manager.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -67,6 +67,104 @@ fn get_sidecar_path() -> Option<std::path::PathBuf> {
|
||||
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
|
||||
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<serde_json::Value>,
|
||||
config: Option<PythonExecutorConfig>,
|
||||
) -> 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() {
|
||||
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
|
||||
|
||||
@@ -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 (
|
||||
<Routes>
|
||||
@@ -31,6 +32,7 @@ function App() {
|
||||
<Route path="/ai-video" element={<AIVideoPage />} />
|
||||
<Route path="/text-video-generator" element={<TextVideoGeneratorPage />} />
|
||||
<Route path="/python-core-test" element={<PythonCoreTestPage />} />
|
||||
<Route path="/python-env-manager" element={<PythonEnvManagerPage />} />
|
||||
<Route path="/templates" element={<TemplateManagePage />} />
|
||||
<Route path="/templates/:templateId" element={<TemplateDetailPage />} />
|
||||
<Route path="/resource-categories" element={<ResourceCategoryPage />} />
|
||||
|
||||
@@ -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: '设置' },
|
||||
]
|
||||
|
||||
|
||||
400
src/pages/PythonEnvManagerPage.tsx
Normal file
400
src/pages/PythonEnvManagerPage.tsx
Normal 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
|
||||
Reference in New Issue
Block a user