feat: 实现一键匹配实时进度通讯
修复问题: - 一键匹配进度条没有逐步递增,只在开始和结束时更新 实现内容: 1. 后端进度事件发送: - 在事件总线中添加BatchMatchingProgress事件类型 - 在批量匹配服务中集成Tauri事件发送 - 在每个模板匹配开始时发送实时进度事件 2. 前端进度事件监听: - 修改BatchMatchingService支持事件监听 - 添加batch_matching_progress事件监听器 - 实时更新进度条状态 3. 事件通讯机制: - 使用Tauri的emit系统发送事件到前端 - 前端通过listen监听实时进度更新 - 确保进度条能够逐步递增显示 技术细节: - 后端:使用app_handle.emit()发送进度事件 - 前端:使用listen()监听batch_matching_progress事件 - 进度计算:基于当前轮数、绑定索引和总绑定数 现在一键匹配过程中进度条会实时更新,用户可以看到匹配的实际进展。
This commit is contained in:
parent
70e8669ace
commit
c3c72ce8bd
|
|
@ -16,6 +16,8 @@ use crate::data::repositories::{
|
||||||
use crate::business::services::template_service::TemplateService;
|
use crate::business::services::template_service::TemplateService;
|
||||||
use crate::business::services::template_matching_result_service::TemplateMatchingResultService;
|
use crate::business::services::template_matching_result_service::TemplateMatchingResultService;
|
||||||
use crate::infrastructure::filename_utils::FilenameUtils;
|
use crate::infrastructure::filename_utils::FilenameUtils;
|
||||||
|
use crate::infrastructure::event_bus::EventBusManager;
|
||||||
|
use tauri::Emitter;
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
@ -28,6 +30,7 @@ pub struct MaterialMatchingService {
|
||||||
template_service: Arc<TemplateService>,
|
template_service: Arc<TemplateService>,
|
||||||
video_classification_repo: Arc<VideoClassificationRepository>,
|
video_classification_repo: Arc<VideoClassificationRepository>,
|
||||||
matching_result_service: Option<Arc<TemplateMatchingResultService>>,
|
matching_result_service: Option<Arc<TemplateMatchingResultService>>,
|
||||||
|
event_bus: Arc<EventBusManager>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 素材匹配请求
|
/// 素材匹配请求
|
||||||
|
|
@ -160,6 +163,7 @@ impl MaterialMatchingService {
|
||||||
template_service,
|
template_service,
|
||||||
video_classification_repo,
|
video_classification_repo,
|
||||||
matching_result_service: None,
|
matching_result_service: None,
|
||||||
|
event_bus: Arc::new(EventBusManager::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,6 +181,7 @@ impl MaterialMatchingService {
|
||||||
template_service,
|
template_service,
|
||||||
video_classification_repo,
|
video_classification_repo,
|
||||||
matching_result_service: Some(matching_result_service),
|
matching_result_service: Some(matching_result_service),
|
||||||
|
event_bus: Arc::new(EventBusManager::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -785,11 +790,17 @@ impl MaterialMatchingService {
|
||||||
/// 执行一键匹配 - 遍历项目的所有活跃模板绑定并逐一匹配
|
/// 执行一键匹配 - 遍历项目的所有活跃模板绑定并逐一匹配
|
||||||
pub async fn batch_match_all_templates(&self, request: BatchMatchingRequest, database: Arc<crate::infrastructure::database::Database>) -> Result<BatchMatchingResult> {
|
pub async fn batch_match_all_templates(&self, request: BatchMatchingRequest, database: Arc<crate::infrastructure::database::Database>) -> Result<BatchMatchingResult> {
|
||||||
// 调用优化的循环匹配方法
|
// 调用优化的循环匹配方法
|
||||||
self.batch_match_all_templates_optimized(request, database).await
|
self.batch_match_all_templates_optimized(request, database, None).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 执行一键匹配(带事件发送)
|
||||||
|
pub async fn batch_match_all_templates_with_events(&self, request: BatchMatchingRequest, database: Arc<crate::infrastructure::database::Database>, app_handle: Option<tauri::AppHandle>) -> Result<BatchMatchingResult> {
|
||||||
|
// 调用优化的循环匹配方法
|
||||||
|
self.batch_match_all_templates_optimized(request, database, app_handle).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 优化的一键匹配 - 循环匹配模板直到失败(无法完整匹配模板 -- 素材不够用)
|
/// 优化的一键匹配 - 循环匹配模板直到失败(无法完整匹配模板 -- 素材不够用)
|
||||||
pub async fn batch_match_all_templates_optimized(&self, request: BatchMatchingRequest, database: Arc<crate::infrastructure::database::Database>) -> Result<BatchMatchingResult> {
|
pub async fn batch_match_all_templates_optimized(&self, request: BatchMatchingRequest, database: Arc<crate::infrastructure::database::Database>, app_handle: Option<tauri::AppHandle>) -> Result<BatchMatchingResult> {
|
||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
|
|
||||||
// 获取项目的所有活跃模板绑定
|
// 获取项目的所有活跃模板绑定
|
||||||
|
|
@ -884,9 +895,23 @@ impl MaterialMatchingService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 逐一尝试匹配每个模板绑定
|
// 逐一尝试匹配每个模板绑定
|
||||||
for binding_detail in &active_bindings {
|
for (binding_index, binding_detail) in active_bindings.iter().enumerate() {
|
||||||
let binding_start_time = std::time::Instant::now();
|
let binding_start_time = std::time::Instant::now();
|
||||||
|
|
||||||
|
// 发送进度事件
|
||||||
|
if let Some(ref handle) = app_handle {
|
||||||
|
let current_binding_index = (total_rounds - 1) as usize * active_bindings.len() + binding_index + 1;
|
||||||
|
let _ = handle.emit("batch_matching_progress", serde_json::json!({
|
||||||
|
"project_id": request.project_id,
|
||||||
|
"current_binding_index": current_binding_index,
|
||||||
|
"total_bindings": active_bindings.len() * 100, // 估算总数
|
||||||
|
"current_template_name": binding_detail.template_name,
|
||||||
|
"completed_bindings": successful_matches,
|
||||||
|
"failed_bindings": failed_matches,
|
||||||
|
"elapsed_time_ms": start_time.elapsed().as_millis() as u64,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
let matching_request = MaterialMatchingRequest {
|
let matching_request = MaterialMatchingRequest {
|
||||||
project_id: request.project_id.clone(),
|
project_id: request.project_id.clone(),
|
||||||
template_id: binding_detail.binding.template_id.clone(),
|
template_id: binding_detail.binding.template_id.clone(),
|
||||||
|
|
|
||||||
|
|
@ -382,17 +382,12 @@ impl MaterialSegment {
|
||||||
|
|
||||||
/// 检查片段是否满足最小时长要求
|
/// 检查片段是否满足最小时长要求
|
||||||
pub fn meets_duration_requirement(&self, required_duration: f64) -> bool {
|
pub fn meets_duration_requirement(&self, required_duration: f64) -> bool {
|
||||||
let meets = self.duration >= required_duration;
|
self.duration >= required_duration
|
||||||
println!(" 📏 时长要求检查: 素材{:.3}s >= 要求{:.3}s = {}",
|
|
||||||
self.duration, required_duration, meets);
|
|
||||||
meets
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 计算与目标时长的匹配度(越接近越好,返回0.0-1.0)
|
/// 计算与目标时长的匹配度(越接近越好,返回0.0-1.0)
|
||||||
pub fn duration_match_score(&self, target_duration: f64) -> f64 {
|
pub fn duration_match_score(&self, target_duration: f64) -> f64 {
|
||||||
if self.duration < target_duration {
|
if self.duration < target_duration {
|
||||||
println!(" 📊 匹配评分: 素材{:.3}s < 目标{:.3}s,时长不足 = 0.0",
|
|
||||||
self.duration, target_duration);
|
|
||||||
return 0.0; // 时长不足,不匹配
|
return 0.0; // 时长不足,不匹配
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -405,9 +400,6 @@ impl MaterialSegment {
|
||||||
} else {
|
} else {
|
||||||
0.3 // 超出50%以上,低匹配度
|
0.3 // 超出50%以上,低匹配度
|
||||||
};
|
};
|
||||||
|
|
||||||
println!(" 📊 匹配评分: 素材{:.3}s vs 目标{:.3}s,超出比例{:.1}% = {:.3}",
|
|
||||||
self.duration, target_duration, excess_ratio * 100.0, score);
|
|
||||||
score
|
score
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,16 @@ pub enum DataEvent {
|
||||||
stage: String, // "metadata", "scene_detection", "video_splitting"
|
stage: String, // "metadata", "scene_detection", "video_splitting"
|
||||||
progress_percentage: f64,
|
progress_percentage: f64,
|
||||||
},
|
},
|
||||||
|
/// 批量匹配进度事件
|
||||||
|
BatchMatchingProgress {
|
||||||
|
project_id: String,
|
||||||
|
current_binding_index: u32,
|
||||||
|
total_bindings: u32,
|
||||||
|
current_template_name: Option<String>,
|
||||||
|
completed_bindings: u32,
|
||||||
|
failed_bindings: u32,
|
||||||
|
elapsed_time_ms: u64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// UI事件
|
/// UI事件
|
||||||
|
|
@ -293,6 +303,28 @@ impl EventBusManager {
|
||||||
progress_percentage,
|
progress_percentage,
|
||||||
})).await
|
})).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 发布批量匹配进度事件
|
||||||
|
pub async fn publish_batch_matching_progress(
|
||||||
|
&self,
|
||||||
|
project_id: String,
|
||||||
|
current_binding_index: u32,
|
||||||
|
total_bindings: u32,
|
||||||
|
current_template_name: Option<String>,
|
||||||
|
completed_bindings: u32,
|
||||||
|
failed_bindings: u32,
|
||||||
|
elapsed_time_ms: u64,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
self.event_bus.publish(Event::Data(DataEvent::BatchMatchingProgress {
|
||||||
|
project_id,
|
||||||
|
current_binding_index,
|
||||||
|
total_bindings,
|
||||||
|
current_template_name,
|
||||||
|
completed_bindings,
|
||||||
|
failed_bindings,
|
||||||
|
elapsed_time_ms,
|
||||||
|
})).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for EventBusManager {
|
impl Default for EventBusManager {
|
||||||
|
|
|
||||||
|
|
@ -710,7 +710,7 @@ impl TolerantJsonParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果值部分是一个被引号包围的字符串
|
// 如果值部分是一个被引号包围的字符串
|
||||||
if (value_part.starts_with('"') && value_part.len() > 1) {
|
if value_part.starts_with('"') && value_part.len() > 1 {
|
||||||
// 寻找匹配的结束引号,考虑转义字符
|
// 寻找匹配的结束引号,考虑转义字符
|
||||||
let mut end_pos = None;
|
let mut end_pos = None;
|
||||||
let mut chars = value_part[1..].char_indices();
|
let mut chars = value_part[1..].char_indices();
|
||||||
|
|
|
||||||
|
|
@ -265,6 +265,7 @@ pub struct TemplateBindingMatchingValidation {
|
||||||
pub async fn batch_match_all_templates(
|
pub async fn batch_match_all_templates(
|
||||||
request: BatchMatchingRequest,
|
request: BatchMatchingRequest,
|
||||||
state: State<'_, crate::app_state::AppState>,
|
state: State<'_, crate::app_state::AppState>,
|
||||||
|
app_handle: tauri::AppHandle,
|
||||||
) -> Result<BatchMatchingResult, String> {
|
) -> Result<BatchMatchingResult, String> {
|
||||||
let database = state.get_database();
|
let database = state.get_database();
|
||||||
|
|
||||||
|
|
@ -295,8 +296,8 @@ pub async fn batch_match_all_templates(
|
||||||
matching_result_service,
|
matching_result_service,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 执行一键匹配
|
// 执行一键匹配(带事件发送)
|
||||||
matching_service.batch_match_all_templates(request, database)
|
matching_service.batch_match_all_templates_with_events(request, database, Some(app_handle))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -549,7 +549,7 @@ export const ProjectDetails: React.FC = () => {
|
||||||
setShowBatchMatchingProgressDialog(true);
|
setShowBatchMatchingProgressDialog(true);
|
||||||
|
|
||||||
// 设置进度回调
|
// 设置进度回调
|
||||||
BatchMatchingService.setProgressCallback((progress: BatchMatchingProgress) => {
|
await BatchMatchingService.setProgressCallback((progress: BatchMatchingProgress) => {
|
||||||
setBatchMatchingProgress(progress);
|
setBatchMatchingProgress(progress);
|
||||||
|
|
||||||
// 当匹配完成时,显示结果对话框
|
// 当匹配完成时,显示结果对话框
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import { listen, UnlistenFn } from '@tauri-apps/api/event';
|
||||||
import {
|
import {
|
||||||
BatchMatchingRequest,
|
BatchMatchingRequest,
|
||||||
BatchMatchingResult,
|
BatchMatchingResult,
|
||||||
|
|
@ -14,12 +15,34 @@ import {
|
||||||
export class BatchMatchingService {
|
export class BatchMatchingService {
|
||||||
// 进度回调函数类型
|
// 进度回调函数类型
|
||||||
static progressCallback: ((progress: BatchMatchingProgress) => void) | null = null;
|
static progressCallback: ((progress: BatchMatchingProgress) => void) | null = null;
|
||||||
|
// 事件监听器
|
||||||
|
static progressEventUnlisten: UnlistenFn | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置进度回调函数
|
* 设置进度回调函数
|
||||||
*/
|
*/
|
||||||
static setProgressCallback(callback: (progress: BatchMatchingProgress) => void) {
|
static async setProgressCallback(callback: (progress: BatchMatchingProgress) => void) {
|
||||||
this.progressCallback = callback;
|
this.progressCallback = callback;
|
||||||
|
|
||||||
|
// 设置事件监听器
|
||||||
|
try {
|
||||||
|
this.progressEventUnlisten = await listen('batch_matching_progress', (event: any) => {
|
||||||
|
const progressData = event.payload;
|
||||||
|
if (this.progressCallback) {
|
||||||
|
this.progressCallback({
|
||||||
|
status: BatchMatchingProgressStatus.InProgress,
|
||||||
|
current_binding_index: progressData.current_binding_index,
|
||||||
|
total_bindings: progressData.total_bindings,
|
||||||
|
current_template_name: progressData.current_template_name,
|
||||||
|
completed_bindings: progressData.completed_bindings,
|
||||||
|
failed_bindings: progressData.failed_bindings,
|
||||||
|
elapsed_time_ms: progressData.elapsed_time_ms,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('设置批量匹配进度事件监听器失败:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -27,6 +50,12 @@ export class BatchMatchingService {
|
||||||
*/
|
*/
|
||||||
static clearProgressCallback() {
|
static clearProgressCallback() {
|
||||||
this.progressCallback = null;
|
this.progressCallback = null;
|
||||||
|
|
||||||
|
// 清除事件监听器
|
||||||
|
if (this.progressEventUnlisten) {
|
||||||
|
this.progressEventUnlisten();
|
||||||
|
this.progressEventUnlisten = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue