feat: 更通用的 带进度条的 python通信方式
This commit is contained in:
242
docs/python_executor_progress.md
Normal file
242
docs/python_executor_progress.md
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
# Python Executor with Progress Support
|
||||||
|
|
||||||
|
通用的Python执行器,支持实时进度回调和JSON-RPC通信。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- ✅ 支持多种Python命令 (python, python3, py)
|
||||||
|
- ✅ UTF-8编码处理
|
||||||
|
- ✅ 并发stdout/stderr读取
|
||||||
|
- ✅ JSON-RPC消息解析
|
||||||
|
- ✅ 实时进度回调
|
||||||
|
- ✅ Tauri事件发射
|
||||||
|
- ✅ 超时处理
|
||||||
|
- ✅ 错误处理
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 1. 基本用法(无进度)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use crate::python_executor::execute_python_command;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn my_command(app: AppHandle) -> Result<String, String> {
|
||||||
|
let args = vec![
|
||||||
|
"-m".to_string(),
|
||||||
|
"my_python_module".to_string(),
|
||||||
|
"--action".to_string(),
|
||||||
|
"do_something".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
execute_python_command(app, &args, None).await
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 使用Tauri事件进度回调
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use crate::python_executor::execute_python_with_events;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn my_command_with_progress(app: AppHandle) -> Result<String, String> {
|
||||||
|
let args = vec![
|
||||||
|
"-m".to_string(),
|
||||||
|
"my_python_module".to_string(),
|
||||||
|
"--action".to_string(),
|
||||||
|
"long_running_task".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// 进度将通过 "my-task-progress" 事件发送到前端
|
||||||
|
execute_python_with_events(app, &args, None, "my-task-progress").await
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 使用函数回调
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use crate::python_executor::{execute_python_with_callback, PythonProgress};
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn my_command_with_callback(app: AppHandle) -> Result<String, String> {
|
||||||
|
let args = vec![
|
||||||
|
"-m".to_string(),
|
||||||
|
"my_python_module".to_string(),
|
||||||
|
"--action".to_string(),
|
||||||
|
"process_data".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// 自定义进度处理
|
||||||
|
let progress_callback = |progress: PythonProgress| {
|
||||||
|
println!("Progress: {}% - {}", progress.progress, progress.message);
|
||||||
|
// 可以在这里添加自定义逻辑,如数据库记录、日志等
|
||||||
|
};
|
||||||
|
|
||||||
|
execute_python_with_callback(app, &args, None, progress_callback).await
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 高级用法(自定义回调)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use crate::python_executor::{execute_python_command_with_progress, ProgressCallback, PythonProgress};
|
||||||
|
|
||||||
|
struct CustomProgressHandler {
|
||||||
|
task_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProgressCallback for CustomProgressHandler {
|
||||||
|
fn on_progress(&self, progress: PythonProgress) {
|
||||||
|
// 自定义进度处理逻辑
|
||||||
|
println!("Task {}: {}% - {}", self.task_id, progress.progress, progress.message);
|
||||||
|
|
||||||
|
// 可以发送到数据库、文件、其他服务等
|
||||||
|
if progress.step == "complete" {
|
||||||
|
println!("Task {} completed!", self.task_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn my_advanced_command(app: AppHandle, task_id: String) -> Result<String, String> {
|
||||||
|
let args = vec![
|
||||||
|
"-m".to_string(),
|
||||||
|
"my_python_module".to_string(),
|
||||||
|
"--task-id".to_string(),
|
||||||
|
task_id.clone(),
|
||||||
|
];
|
||||||
|
|
||||||
|
let progress_handler = CustomProgressHandler { task_id };
|
||||||
|
execute_python_command_with_progress(app, &args, None, Some(progress_handler)).await
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Python端实现
|
||||||
|
|
||||||
|
Python脚本需要使用JSON-RPC协议发送进度信息:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from python_core.utils.jsonrpc import create_response_handler, create_progress_reporter
|
||||||
|
|
||||||
|
def my_long_running_task():
|
||||||
|
rpc = create_response_handler("task_id")
|
||||||
|
progress = create_progress_reporter()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 报告开始
|
||||||
|
progress.step("start", "开始处理任务...")
|
||||||
|
|
||||||
|
# 处理步骤1
|
||||||
|
progress.report("step1", 25.0, "正在处理步骤1...")
|
||||||
|
# ... 实际处理逻辑 ...
|
||||||
|
|
||||||
|
# 处理步骤2
|
||||||
|
progress.report("step2", 50.0, "正在处理步骤2...")
|
||||||
|
# ... 实际处理逻辑 ...
|
||||||
|
|
||||||
|
# 处理步骤3
|
||||||
|
progress.report("step3", 75.0, "正在处理步骤3...")
|
||||||
|
# ... 实际处理逻辑 ...
|
||||||
|
|
||||||
|
# 完成
|
||||||
|
progress.complete("任务完成!")
|
||||||
|
|
||||||
|
# 发送最终结果
|
||||||
|
result = {
|
||||||
|
"status": True,
|
||||||
|
"message": "任务成功完成",
|
||||||
|
"data": {...}
|
||||||
|
}
|
||||||
|
rpc.success(result)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
progress.error(f"任务失败: {str(e)}")
|
||||||
|
rpc.error(-32603, "任务执行失败", str(e))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
my_long_running_task()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 前端使用
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { listen } from '@tauri-apps/api/event'
|
||||||
|
|
||||||
|
// 监听进度事件
|
||||||
|
const unlisten = await listen('my-task-progress', (event) => {
|
||||||
|
const progress = event.payload as {
|
||||||
|
step: string
|
||||||
|
progress: number
|
||||||
|
message: string
|
||||||
|
details?: any
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Progress: ${progress.progress}% - ${progress.message}`)
|
||||||
|
|
||||||
|
// 更新UI
|
||||||
|
updateProgressBar(progress.progress)
|
||||||
|
updateStatusMessage(progress.message)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 执行命令
|
||||||
|
try {
|
||||||
|
const result = await invoke('my_command_with_progress')
|
||||||
|
console.log('Task completed:', result)
|
||||||
|
} finally {
|
||||||
|
unlisten() // 清理监听器
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 进度数据结构
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct PythonProgress {
|
||||||
|
pub step: String, // 当前步骤名称
|
||||||
|
pub progress: f64, // 进度百分比 (0-100, -1表示不确定)
|
||||||
|
pub message: String, // 进度消息
|
||||||
|
pub details: Option<serde_json::Value>, // 额外详情
|
||||||
|
pub timestamp: f64, // 时间戳
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置选项
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct PythonExecutorConfig {
|
||||||
|
pub timeout_seconds: u64, // 超时时间(秒)
|
||||||
|
pub working_directory: Option<PathBuf>, // 工作目录
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认配置:10分钟超时,当前目录
|
||||||
|
let config = PythonExecutorConfig::default();
|
||||||
|
|
||||||
|
// 自定义配置
|
||||||
|
let config = PythonExecutorConfig {
|
||||||
|
timeout_seconds: 1800, // 30分钟
|
||||||
|
working_directory: Some("/path/to/workdir".into()),
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **选择合适的回调方式**:
|
||||||
|
- 简单任务:使用 `execute_python_command`
|
||||||
|
- 需要前端进度显示:使用 `execute_python_with_events`
|
||||||
|
- 需要自定义处理:使用 `execute_python_with_callback`
|
||||||
|
|
||||||
|
2. **Python端进度报告**:
|
||||||
|
- 使用有意义的步骤名称
|
||||||
|
- 提供清晰的进度消息
|
||||||
|
- 合理设置进度百分比
|
||||||
|
- 在关键节点报告进度
|
||||||
|
|
||||||
|
3. **错误处理**:
|
||||||
|
- Python端使用try-catch包装
|
||||||
|
- 通过JSON-RPC发送错误信息
|
||||||
|
- Rust端处理超时和进程错误
|
||||||
|
|
||||||
|
4. **性能考虑**:
|
||||||
|
- 不要过于频繁地报告进度
|
||||||
|
- 避免在进度回调中执行耗时操作
|
||||||
|
- 合理设置超时时间
|
||||||
@@ -1,22 +1,16 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::python_executor::execute_python_command;
|
use crate::python_executor::{execute_python_command, execute_python_with_events};
|
||||||
use tauri::{AppHandle, Emitter};
|
use tauri::AppHandle;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct BatchImportRequest {
|
pub struct BatchImportRequest {
|
||||||
pub source_folder: String,
|
pub source_folder: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Clone)]
|
// Re-export PythonProgress as ImportProgress for backward compatibility
|
||||||
pub struct ImportProgress {
|
// Note: This is used by frontend TypeScript definitions
|
||||||
pub step: String,
|
#[allow(unused_imports)]
|
||||||
pub progress: f64,
|
pub use crate::python_executor::PythonProgress as ImportProgress;
|
||||||
pub message: String,
|
|
||||||
pub current_template: Option<String>,
|
|
||||||
pub total_templates: Option<i32>,
|
|
||||||
pub processed_templates: Option<i32>,
|
|
||||||
pub timestamp: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct TemplateInfo {
|
pub struct TemplateInfo {
|
||||||
@@ -65,29 +59,8 @@ pub async fn batch_import_templates_with_progress(
|
|||||||
request.source_folder,
|
request.source_folder,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Create a progress callback that emits events to the frontend
|
// Execute with progress monitoring via Tauri events
|
||||||
let app_clone = app.clone();
|
execute_python_with_events(app, &args, None, "template-import-progress").await
|
||||||
let progress_callback = move |progress: ImportProgress| {
|
|
||||||
let _ = app_clone.emit("template-import-progress", &progress);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Execute with progress monitoring
|
|
||||||
execute_python_command_with_progress(app, &args, None, progress_callback).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn execute_python_command_with_progress<F>(
|
|
||||||
app: AppHandle,
|
|
||||||
args: &[String],
|
|
||||||
config: Option<crate::python_executor::PythonExecutorConfig>,
|
|
||||||
_progress_callback: F,
|
|
||||||
) -> Result<String, String>
|
|
||||||
where
|
|
||||||
F: Fn(ImportProgress) + Send + 'static,
|
|
||||||
{
|
|
||||||
// For now, we'll use the existing execute_python_command
|
|
||||||
// and parse progress from JSON-RPC notifications
|
|
||||||
// TODO: Enhance python_executor to support progress callbacks
|
|
||||||
execute_python_command(app, args, config).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -129,3 +102,28 @@ pub async fn delete_template(app: tauri::AppHandle, template_id: String) -> Resu
|
|||||||
|
|
||||||
execute_python_command(app, &args, None).await
|
execute_python_command(app, &args, None).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Example: AI Video generation with progress
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn generate_video_with_progress(
|
||||||
|
app: AppHandle,
|
||||||
|
image_path: String,
|
||||||
|
prompt: String,
|
||||||
|
output_path: String,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let args = vec![
|
||||||
|
"-m".to_string(),
|
||||||
|
"python_core.ai_video.video_generator".to_string(),
|
||||||
|
"--image_path".to_string(),
|
||||||
|
image_path,
|
||||||
|
"--prompt".to_string(),
|
||||||
|
prompt,
|
||||||
|
"--output_path".to_string(),
|
||||||
|
output_path,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Execute with progress monitoring via Tauri events
|
||||||
|
// Frontend can listen to "video-generation-progress" event
|
||||||
|
execute_python_with_events(app, &args, None, "video-generation-progress").await
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,68 @@ use std::time::{Duration, Instant};
|
|||||||
use std::thread;
|
use std::thread;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tauri::{AppHandle, Emitter};
|
||||||
|
|
||||||
|
/// Generic progress information from Python processes
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct PythonProgress {
|
||||||
|
pub step: String,
|
||||||
|
pub progress: f64, // -1 for indeterminate, 0-100 for percentage
|
||||||
|
pub message: String,
|
||||||
|
pub details: Option<serde_json::Value>,
|
||||||
|
pub timestamp: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Progress callback trait for handling Python process progress
|
||||||
|
pub trait ProgressCallback: Send + Sync {
|
||||||
|
fn on_progress(&self, progress: PythonProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple function-based progress callback
|
||||||
|
pub struct FunctionCallback<F>
|
||||||
|
where
|
||||||
|
F: Fn(PythonProgress) + Send + Sync,
|
||||||
|
{
|
||||||
|
callback: F,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> FunctionCallback<F>
|
||||||
|
where
|
||||||
|
F: Fn(PythonProgress) + Send + Sync,
|
||||||
|
{
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn new(callback: F) -> Self {
|
||||||
|
Self { callback }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> ProgressCallback for FunctionCallback<F>
|
||||||
|
where
|
||||||
|
F: Fn(PythonProgress) + Send + Sync,
|
||||||
|
{
|
||||||
|
fn on_progress(&self, progress: PythonProgress) {
|
||||||
|
(self.callback)(progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tauri event-based progress callback
|
||||||
|
pub struct TauriEventCallback {
|
||||||
|
app: AppHandle,
|
||||||
|
event_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TauriEventCallback {
|
||||||
|
pub fn new(app: AppHandle, event_name: String) -> Self {
|
||||||
|
Self { app, event_name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProgressCallback for TauriEventCallback {
|
||||||
|
fn on_progress(&self, progress: PythonProgress) {
|
||||||
|
let _ = self.app.emit(&self.event_name, &progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Configuration for Python command execution
|
/// Configuration for Python command execution
|
||||||
pub struct PythonExecutorConfig {
|
pub struct PythonExecutorConfig {
|
||||||
@@ -20,7 +82,28 @@ impl Default for PythonExecutorConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a Python command with the given arguments
|
/// Execute a Python command with progress callback support
|
||||||
|
///
|
||||||
|
/// This function handles:
|
||||||
|
/// - Multiple Python command attempts (python, python3, py)
|
||||||
|
/// - UTF-8 encoding setup
|
||||||
|
/// - Concurrent stdout/stderr reading
|
||||||
|
/// - JSON-RPC message parsing
|
||||||
|
/// - Progress notifications via callback
|
||||||
|
/// - Timeout handling
|
||||||
|
pub async fn execute_python_command_with_progress<P>(
|
||||||
|
app: tauri::AppHandle,
|
||||||
|
args: &[String],
|
||||||
|
config: Option<PythonExecutorConfig>,
|
||||||
|
progress_callback: Option<P>,
|
||||||
|
) -> Result<String, String>
|
||||||
|
where
|
||||||
|
P: ProgressCallback + 'static,
|
||||||
|
{
|
||||||
|
execute_python_internal(app, args, config, progress_callback).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute a Python command with the given arguments (legacy interface)
|
||||||
///
|
///
|
||||||
/// This function handles:
|
/// This function handles:
|
||||||
/// - Multiple Python command attempts (python, python3, py)
|
/// - Multiple Python command attempts (python, python3, py)
|
||||||
@@ -30,10 +113,49 @@ impl Default for PythonExecutorConfig {
|
|||||||
/// - Timeout handling
|
/// - Timeout handling
|
||||||
/// - Detailed error reporting
|
/// - Detailed error reporting
|
||||||
pub async fn execute_python_command(
|
pub async fn execute_python_command(
|
||||||
_app: tauri::AppHandle,
|
app: tauri::AppHandle,
|
||||||
args: &[String],
|
args: &[String],
|
||||||
config: Option<PythonExecutorConfig>
|
config: Option<PythonExecutorConfig>
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
|
execute_python_internal(app, args, config, None::<FunctionCallback<fn(PythonProgress)>>).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute Python command with a simple function callback
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn execute_python_with_callback<F>(
|
||||||
|
app: tauri::AppHandle,
|
||||||
|
args: &[String],
|
||||||
|
config: Option<PythonExecutorConfig>,
|
||||||
|
callback: F,
|
||||||
|
) -> Result<String, String>
|
||||||
|
where
|
||||||
|
F: Fn(PythonProgress) + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
let progress_callback = FunctionCallback::new(callback);
|
||||||
|
execute_python_command_with_progress(app, args, config, Some(progress_callback)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute Python command with Tauri event emission
|
||||||
|
pub async fn execute_python_with_events(
|
||||||
|
app: tauri::AppHandle,
|
||||||
|
args: &[String],
|
||||||
|
config: Option<PythonExecutorConfig>,
|
||||||
|
event_name: &str,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let progress_callback = TauriEventCallback::new(app.clone(), event_name.to_string());
|
||||||
|
execute_python_command_with_progress(app, args, config, Some(progress_callback)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal implementation for Python command execution
|
||||||
|
async fn execute_python_internal<P>(
|
||||||
|
_app: tauri::AppHandle,
|
||||||
|
args: &[String],
|
||||||
|
config: Option<PythonExecutorConfig>,
|
||||||
|
progress_callback: Option<P>,
|
||||||
|
) -> Result<String, String>
|
||||||
|
where
|
||||||
|
P: ProgressCallback + 'static,
|
||||||
|
{
|
||||||
let config = config.unwrap_or_default();
|
let config = config.unwrap_or_default();
|
||||||
|
|
||||||
println!("Executing Python command with args: {:?}", args);
|
println!("Executing Python command with args: {:?}", args);
|
||||||
@@ -109,9 +231,13 @@ pub async fn execute_python_command(
|
|||||||
let error_messages = Arc::new(Mutex::new(Vec::new()));
|
let error_messages = Arc::new(Mutex::new(Vec::new()));
|
||||||
let final_result = Arc::new(Mutex::new(None::<String>));
|
let final_result = Arc::new(Mutex::new(None::<String>));
|
||||||
|
|
||||||
|
// Wrap progress callback in Arc for thread sharing
|
||||||
|
let progress_callback = Arc::new(progress_callback);
|
||||||
|
|
||||||
// Spawn thread for reading stdout
|
// Spawn thread for reading stdout
|
||||||
let progress_messages_clone = Arc::clone(&progress_messages);
|
let progress_messages_clone = Arc::clone(&progress_messages);
|
||||||
let final_result_clone = Arc::clone(&final_result);
|
let final_result_clone = Arc::clone(&final_result);
|
||||||
|
let progress_callback_clone = Arc::clone(&progress_callback);
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let mut reader = BufReader::new(stdout);
|
let mut reader = BufReader::new(stdout);
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
@@ -137,9 +263,45 @@ pub async fn execute_python_command(
|
|||||||
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(json_str) {
|
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(json_str) {
|
||||||
if let Some(method) = json_value.get("method") {
|
if let Some(method) = json_value.get("method") {
|
||||||
if method == "progress" {
|
if method == "progress" {
|
||||||
|
// Store progress message
|
||||||
if let Ok(mut messages) = progress_messages_clone.lock() {
|
if let Ok(mut messages) = progress_messages_clone.lock() {
|
||||||
messages.push(json_str.to_string());
|
messages.push(json_str.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call progress callback if available
|
||||||
|
if let Some(ref callback) = *progress_callback_clone {
|
||||||
|
if let Some(params) = json_value.get("params") {
|
||||||
|
// Try to parse as PythonProgress
|
||||||
|
if let Ok(progress) = serde_json::from_value::<PythonProgress>(params.clone()) {
|
||||||
|
callback.on_progress(progress);
|
||||||
|
} else {
|
||||||
|
// Fallback: create basic progress from available data
|
||||||
|
let step = params.get("step")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
.to_string();
|
||||||
|
let message = params.get("message")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string();
|
||||||
|
let progress_val = params.get("progress")
|
||||||
|
.and_then(|v| v.as_f64())
|
||||||
|
.unwrap_or(-1.0);
|
||||||
|
|
||||||
|
let progress = PythonProgress {
|
||||||
|
step,
|
||||||
|
progress: progress_val,
|
||||||
|
message,
|
||||||
|
details: Some(params.clone()),
|
||||||
|
timestamp: std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_secs_f64(),
|
||||||
|
};
|
||||||
|
callback.on_progress(progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
println!("Progress: {}", json_str);
|
println!("Progress: {}", json_str);
|
||||||
}
|
}
|
||||||
} else if json_value.get("result").is_some() || json_value.get("error").is_some() {
|
} else if json_value.get("result").is_some() || json_value.get("error").is_some() {
|
||||||
@@ -148,10 +310,8 @@ pub async fn execute_python_command(
|
|||||||
// Extract the result or error content from JSON-RPC
|
// Extract the result or error content from JSON-RPC
|
||||||
if let Some(result_content) = json_value.get("result") {
|
if let Some(result_content) = json_value.get("result") {
|
||||||
*result = Some(result_content.to_string());
|
*result = Some(result_content.to_string());
|
||||||
println!("JSON-RPC result extracted: {}", result_content);
|
|
||||||
} else if let Some(error_content) = json_value.get("error") {
|
} else if let Some(error_content) = json_value.get("error") {
|
||||||
*result = Some(format!("{{\"status\":false,\"error\":{}}}", error_content));
|
*result = Some(format!("{{\"status\":false,\"error\":{}}}", error_content));
|
||||||
println!("JSON-RPC error extracted: {}", error_content);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
use crate::python_executor::execute_python_command;
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_get_templates() {
|
|
||||||
let app = tauri::test::mock_app();
|
|
||||||
let args = vec![
|
|
||||||
"-m".to_string(),
|
|
||||||
"python_core.services.template_manager".to_string(),
|
|
||||||
"--action".to_string(),
|
|
||||||
"get_templates".to_string(),
|
|
||||||
];
|
|
||||||
|
|
||||||
match execute_python_command(app, &args, None).await {
|
|
||||||
Ok(result) => {
|
|
||||||
println!("Result: {}", result);
|
|
||||||
// Parse the result to verify it's valid JSON
|
|
||||||
match serde_json::from_str::<serde_json::Value>(&result) {
|
|
||||||
Ok(json) => {
|
|
||||||
println!("Parsed JSON successfully");
|
|
||||||
if let Some(status) = json.get("status") {
|
|
||||||
println!("Status: {}", status);
|
|
||||||
}
|
|
||||||
if let Some(templates) = json.get("templates") {
|
|
||||||
if let Some(array) = templates.as_array() {
|
|
||||||
println!("Found {} templates", array.len());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("Failed to parse JSON: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("Error: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -33,11 +33,9 @@ interface ImportResult {
|
|||||||
|
|
||||||
interface ImportProgress {
|
interface ImportProgress {
|
||||||
step: string
|
step: string
|
||||||
progress: number // 0-100
|
progress: number // -1 for indeterminate, 0-100 for percentage
|
||||||
message: string
|
message: string
|
||||||
currentTemplate?: string
|
details?: any // Additional details from Python
|
||||||
totalTemplates?: number
|
|
||||||
processedTemplates?: number
|
|
||||||
timestamp: number
|
timestamp: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,9 +64,11 @@ const TemplateManagePage: React.FC = () => {
|
|||||||
setTemplates(result.templates || [])
|
setTemplates(result.templates || [])
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to load templates:', result.msg)
|
console.error('Failed to load templates:', result.msg)
|
||||||
|
setTemplates([])
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading templates:', error)
|
console.error('Error loading templates:', error)
|
||||||
|
setTemplates([])
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
@@ -93,25 +93,8 @@ const TemplateManagePage: React.FC = () => {
|
|||||||
setImportLogs(prev => [...prev, `[${new Date().toLocaleTimeString()}] ${progress.message}`])
|
setImportLogs(prev => [...prev, `[${new Date().toLocaleTimeString()}] ${progress.message}`])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate initial progress
|
// Use the new progress-enabled import
|
||||||
onProgress({
|
const result = await TemplateService.batchImportTemplatesWithProgress(folderResult, onProgress)
|
||||||
step: "scanning",
|
|
||||||
progress: 10,
|
|
||||||
message: "正在扫描模板文件夹...",
|
|
||||||
timestamp: Date.now()
|
|
||||||
})
|
|
||||||
|
|
||||||
// For now, use the regular import and simulate progress
|
|
||||||
// TODO: Switch to batchImportTemplatesWithProgress when backend is ready
|
|
||||||
const result = await TemplateService.batchImportTemplates(folderResult)
|
|
||||||
|
|
||||||
// Simulate completion progress
|
|
||||||
onProgress({
|
|
||||||
step: "complete",
|
|
||||||
progress: 100,
|
|
||||||
message: "模板导入完成",
|
|
||||||
timestamp: Date.now()
|
|
||||||
})
|
|
||||||
setImportResult(result)
|
setImportResult(result)
|
||||||
|
|
||||||
if (result.status && result.imported_count > 0) {
|
if (result.status && result.imported_count > 0) {
|
||||||
@@ -426,9 +409,9 @@ const TemplateManagePage: React.FC = () => {
|
|||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-600 mt-2">{importProgress.message}</p>
|
<p className="text-sm text-gray-600 mt-2">{importProgress.message}</p>
|
||||||
{importProgress.processedTemplates !== undefined && importProgress.totalTemplates !== undefined && (
|
{importProgress.details?.processed_templates !== undefined && importProgress.details?.total_templates !== undefined && (
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
<p className="text-xs text-gray-500 mt-1">
|
||||||
已处理: {importProgress.processedTemplates} / {importProgress.totalTemplates}
|
已处理: {importProgress.details.processed_templates} / {importProgress.details.total_templates}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Test script to verify template loading functionality
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
from python_core.services.template_manager import TemplateManager
|
|
||||||
from python_core.utils.jsonrpc import create_response_handler
|
|
||||||
|
|
||||||
def test_get_templates():
|
|
||||||
"""Test getting templates"""
|
|
||||||
rpc = create_response_handler("test")
|
|
||||||
|
|
||||||
try:
|
|
||||||
manager = TemplateManager()
|
|
||||||
templates = manager.get_templates()
|
|
||||||
|
|
||||||
result = {
|
|
||||||
"status": True,
|
|
||||||
"templates": [template.__dict__ for template in templates]
|
|
||||||
}
|
|
||||||
|
|
||||||
rpc.success(result)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
rpc.error(-32603, f"Failed to get templates: {str(e)}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
test_get_templates()
|
|
||||||
Reference in New Issue
Block a user