/** * 素材匹配相关的 Tauri 命令 * 遵循 Tauri 开发规范的 API 设计原则 */ use tauri::{command, State}; use std::sync::Arc; use crate::business::services::material_matching_service::{ MaterialMatchingService, MaterialMatchingRequest, MaterialMatchingResult, BatchMatchingRequest, BatchMatchingResult, }; use crate::business::services::template_service::TemplateService; use crate::business::services::template_matching_result_service::TemplateMatchingResultService; use crate::data::repositories::{ material_repository::MaterialRepository, video_classification_repository::VideoClassificationRepository, template_matching_result_repository::TemplateMatchingResultRepository, }; use crate::data::models::template_matching_result::TemplateMatchingResult; use crate::infrastructure::database::Database; /// 执行素材匹配 #[command] pub async fn execute_material_matching( request: MaterialMatchingRequest, database: State<'_, Arc>, ) -> Result { // 创建服务实例 let material_repo = Arc::new( MaterialRepository::new(database.inner().clone()) .map_err(|e| format!("创建素材仓库失败: {}", e))? ); let template_service = Arc::new(TemplateService::new(database.inner().clone())); let video_classification_repo = Arc::new( VideoClassificationRepository::new(database.inner().clone()) ); let material_usage_repo = Arc::new( crate::data::repositories::material_usage_repository::MaterialUsageRepository::new(database.inner().clone()) ); let ai_classification_repo = Arc::new( crate::data::repositories::ai_classification_repository::AiClassificationRepository::new(database.inner().clone()) ); let ai_classification_service = Arc::new( crate::business::services::ai_classification_service::AiClassificationService::new(ai_classification_repo) ); let matching_service = MaterialMatchingService::new( material_repo, material_usage_repo, template_service, video_classification_repo, ai_classification_service, ); // 执行匹配 matching_service.match_materials(request) .await .map_err(|e| e.to_string()) } /// 执行素材匹配并自动保存结果 #[command] pub async fn execute_material_matching_with_save( request: MaterialMatchingRequest, result_name: String, description: Option, database: State<'_, Arc>, ) -> Result<(MaterialMatchingResult, Option), String> { // 创建服务实例 let material_repo = Arc::new( MaterialRepository::new(database.inner().clone()) .map_err(|e| format!("创建素材仓库失败: {}", e))? ); let template_service = Arc::new(TemplateService::new(database.inner().clone())); let video_classification_repo = Arc::new( VideoClassificationRepository::new(database.inner().clone()) ); // 创建匹配结果服务 let material_usage_repo = Arc::new( crate::data::repositories::material_usage_repository::MaterialUsageRepository::new(database.inner().clone()) ); let matching_result_repo = Arc::new(TemplateMatchingResultRepository::new(database.inner().clone())); let matching_result_service = Arc::new(TemplateMatchingResultService::new(matching_result_repo)); let ai_classification_repo = Arc::new( crate::data::repositories::ai_classification_repository::AiClassificationRepository::new(database.inner().clone()) ); let ai_classification_service = Arc::new( crate::business::services::ai_classification_service::AiClassificationService::new(ai_classification_repo) ); let matching_service = MaterialMatchingService::new_with_result_service( material_repo, material_usage_repo, template_service, video_classification_repo, ai_classification_service, matching_result_service, ); // 执行匹配并保存结果 matching_service.match_materials_and_save(request, result_name, description) .await .map_err(|e| e.to_string()) } /// 获取项目的可用素材统计信息 #[command] pub async fn get_project_material_stats_for_matching( project_id: String, database: State<'_, Arc>, ) -> Result { let material_repo = MaterialRepository::new(database.inner().clone()) .map_err(|e| format!("创建素材仓库失败: {}", e))?; let video_classification_repo = VideoClassificationRepository::new(database.inner().clone()); // 获取项目的所有素材 let materials = material_repo.get_by_project_id(&project_id) .map_err(|e| format!("获取项目素材失败: {}", e))?; let mut total_segments = 0; let mut classified_segments = 0; let mut available_models = std::collections::HashSet::new(); let mut available_categories = std::collections::HashSet::new(); for material in &materials { total_segments += material.segments.len(); // 获取分类记录 let classification_records = video_classification_repo.get_by_material_id(&material.id) .await .map_err(|e| format!("获取分类记录失败: {}", e))?; // 统计已分类的片段 for segment in &material.segments { if classification_records.iter().any(|r| r.segment_id == segment.id) { classified_segments += 1; // 记录分类类别 if let Some(record) = classification_records.iter().find(|r| r.segment_id == segment.id) { available_categories.insert(record.category.clone()); } } } // 记录模特 if let Some(model_id) = &material.model_id { available_models.insert(model_id.clone()); } } Ok(ProjectMaterialMatchingStats { project_id, total_materials: materials.len() as u32, total_segments: total_segments as u32, classified_segments: classified_segments as u32, available_models: available_models.len() as u32, available_categories: available_categories.into_iter().collect(), classification_rate: if total_segments > 0 { classified_segments as f64 / total_segments as f64 } else { 0.0 }, }) } /// 验证模板绑定是否可以进行素材匹配 #[command] pub async fn validate_template_binding_for_matching( binding_id: String, database: State<'_, Arc>, ) -> Result { use crate::data::repositories::project_template_binding_repository::ProjectTemplateBindingRepository; use crate::business::services::template_service::TemplateService; let mut validation_errors = Vec::new(); let mut total_segments = 0; let mut matchable_segments = 0; // 获取模板绑定信息 let binding_repo = ProjectTemplateBindingRepository::new(database.inner().clone()); let binding = match binding_repo.get_by_id(&binding_id) { Ok(Some(binding)) => binding, Ok(None) => { validation_errors.push("模板绑定不存在".to_string()); return Ok(TemplateBindingMatchingValidation { binding_id, is_valid: false, validation_errors, total_segments: 0, matchable_segments: 0, }); } Err(e) => { validation_errors.push(format!("获取模板绑定失败: {}", e)); return Ok(TemplateBindingMatchingValidation { binding_id, is_valid: false, validation_errors, total_segments: 0, matchable_segments: 0, }); } }; // 检查绑定是否激活 if !binding.is_active { validation_errors.push("模板绑定未激活".to_string()); } // 获取模板信息并统计片段 let template_service = TemplateService::new(database.inner().clone()); match template_service.get_template_by_id(&binding.template_id).await { Ok(Some(template)) => { // 统计所有轨道片段 for track in &template.tracks { total_segments += track.segments.len() as u32; // 统计可匹配的片段(非固定素材) for segment in &track.segments { if !segment.matching_rule.is_fixed_material() { matchable_segments += 1; } } } } Ok(None) => { validation_errors.push("关联的模板不存在".to_string()); } Err(e) => { validation_errors.push(format!("获取模板信息失败: {}", e)); } } let is_valid = validation_errors.is_empty(); Ok(TemplateBindingMatchingValidation { binding_id, is_valid, validation_errors, total_segments, matchable_segments, }) } /// 项目素材匹配统计信息 #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct ProjectMaterialMatchingStats { pub project_id: String, pub total_materials: u32, pub total_segments: u32, pub classified_segments: u32, pub available_models: u32, pub available_categories: Vec, pub classification_rate: f64, } /// 模板绑定匹配验证结果 #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct TemplateBindingMatchingValidation { pub binding_id: String, pub is_valid: bool, pub validation_errors: Vec, pub total_segments: u32, pub matchable_segments: u32, } /// 一键匹配所有模板 /// 遍历项目的所有活跃模板绑定并逐一执行匹配 #[tauri::command] pub async fn batch_match_all_templates( request: BatchMatchingRequest, state: State<'_, crate::app_state::AppState>, app_handle: tauri::AppHandle, ) -> Result { let database = state.get_database(); // 创建所需的仓储实例 let material_repo = Arc::new( MaterialRepository::new(database.clone()) .map_err(|e| format!("创建素材仓储失败: {}", e))? ); let template_service = Arc::new(TemplateService::new(database.clone())); let video_classification_repo = Arc::new( VideoClassificationRepository::new(database.clone()) ); // 创建匹配结果服务 let material_usage_repo = Arc::new( crate::data::repositories::material_usage_repository::MaterialUsageRepository::new(database.clone()) ); let matching_result_repo = Arc::new(TemplateMatchingResultRepository::new(database.clone())); let matching_result_service = Arc::new(TemplateMatchingResultService::new(matching_result_repo)); let ai_classification_repo = Arc::new( crate::data::repositories::ai_classification_repository::AiClassificationRepository::new(database.clone()) ); let ai_classification_service = Arc::new( crate::business::services::ai_classification_service::AiClassificationService::new(ai_classification_repo) ); let matching_service = MaterialMatchingService::new_with_result_service( material_repo, material_usage_repo, template_service, video_classification_repo, ai_classification_service, matching_result_service, ); // 执行一键匹配(带事件发送) matching_service.batch_match_all_templates_with_events(request, database, Some(app_handle)) .await .map_err(|e| e.to_string()) }