diff --git a/apps/desktop/src-tauri/src/business/services/async_material_service.rs b/apps/desktop/src-tauri/src/business/services/async_material_service.rs index abe2e7a..f761493 100644 --- a/apps/desktop/src-tauri/src/business/services/async_material_service.rs +++ b/apps/desktop/src-tauri/src/business/services/async_material_service.rs @@ -77,6 +77,7 @@ impl AsyncMaterialService { let file_path_clone = file_path.clone(); let config_clone = config.clone(); let event_bus_clone = Arc::clone(&event_bus); + let model_id_clone = request.model_id.clone(); match Self::process_single_file_async( repository_clone, @@ -84,6 +85,7 @@ impl AsyncMaterialService { &file_path_clone, &config_clone, event_bus_clone, + model_id_clone, ).await { Ok(Some(material)) => { info!( @@ -153,6 +155,7 @@ impl AsyncMaterialService { file_path: &str, config: &MaterialProcessingConfig, event_bus: Arc, + model_id: Option, ) -> Result> { let _timer = PERFORMANCE_MONITOR.start_operation("async_process_single_file"); @@ -190,14 +193,15 @@ impl AsyncMaterialService { // 确定素材类型 let material_type = MaterialType::from_extension(extension); - // 创建素材对象 - let material = Material::new( + // 创建素材对象(带模特绑定) + let material = Material::new_with_model( project_id.to_string(), file_name.clone(), file_path.to_string(), file_size, md5_hash, material_type, + model_id, ); // 保存到数据库 diff --git a/apps/desktop/src-tauri/src/business/services/material_service.rs b/apps/desktop/src-tauri/src/business/services/material_service.rs index 7f35be0..eda5792 100644 --- a/apps/desktop/src-tauri/src/business/services/material_service.rs +++ b/apps/desktop/src-tauri/src/business/services/material_service.rs @@ -46,7 +46,7 @@ impl MaterialService { for file_path in &request.file_paths { debug!(file_path = %file_path, "处理文件"); - match Self::process_single_file(repository, &request.project_id, file_path, config) { + match Self::process_single_file(repository, &request.project_id, file_path, config, request.model_id.clone()) { Ok(Some(material)) => { info!( file_path = %file_path, @@ -98,6 +98,7 @@ impl MaterialService { project_id: &str, file_path: &str, config: &MaterialProcessingConfig, + model_id: Option, ) -> Result> { let _timer = PERFORMANCE_MONITOR.start_operation("process_single_file"); @@ -132,14 +133,15 @@ impl MaterialService { // 确定素材类型 let material_type = MaterialType::from_extension(extension); - // 创建素材对象 - let material = Material::new( + // 创建素材对象(带模特绑定) + let material = Material::new_with_model( project_id.to_string(), file_name, file_path.to_string(), file_size, md5_hash, material_type, + model_id, ); // 保存到数据库 diff --git a/apps/desktop/src-tauri/src/data/models/material.rs b/apps/desktop/src-tauri/src/data/models/material.rs index 258e763..216bfdf 100644 --- a/apps/desktop/src-tauri/src/data/models/material.rs +++ b/apps/desktop/src-tauri/src/data/models/material.rs @@ -141,6 +141,7 @@ pub struct CreateMaterialRequest { pub file_paths: Vec, pub auto_process: bool, pub max_segment_duration: Option, // 最大片段时长(秒) + pub model_id: Option, // 可选的模特绑定ID } /// 视频切分模式 @@ -241,6 +242,37 @@ impl Material { } } + /// 创建新的素材实例(带模特绑定) + pub fn new_with_model( + project_id: String, + name: String, + original_path: String, + file_size: u64, + md5_hash: String, + material_type: MaterialType, + model_id: Option, + ) -> Self { + let now = Utc::now(); + Self { + id: uuid::Uuid::new_v4().to_string(), + project_id, + model_id, + name, + original_path, + file_size, + md5_hash, + material_type, + processing_status: ProcessingStatus::Pending, + metadata: MaterialMetadata::None, + scene_detection: None, + segments: Vec::new(), + created_at: now, + updated_at: now, + processed_at: None, + error_message: None, + } + } + /// 更新处理状态 pub fn update_status(&mut self, status: ProcessingStatus, error_message: Option) { self.processing_status = status; diff --git a/apps/desktop/src/components/MaterialImportDialog.tsx b/apps/desktop/src/components/MaterialImportDialog.tsx index 4bb399f..e4d7254 100644 --- a/apps/desktop/src/components/MaterialImportDialog.tsx +++ b/apps/desktop/src/components/MaterialImportDialog.tsx @@ -3,6 +3,9 @@ import { X, Upload, FileText, AlertCircle, CheckCircle, Loader2 } from 'lucide-r import { listen } from '@tauri-apps/api/event'; import { useMaterialStore } from '../store/materialStore'; import { CreateMaterialRequest, MaterialImportResult } from '../types/material'; +import { Model } from '../types/model'; +import { MaterialModelBindingService } from '../services/materialModelBindingService'; +import { CustomSelect } from './CustomSelect'; interface MaterialImportDialogProps { isOpen: boolean; @@ -46,6 +49,11 @@ export const MaterialImportDialog: React.FC = ({ const [recursiveScan, setRecursiveScan] = useState(true); const [selectedFileTypes, setSelectedFileTypes] = useState([]); + // 模特绑定相关状态 + const [selectedModelId, setSelectedModelId] = useState(''); + const [availableModels, setAvailableModels] = useState([]); + const [loadingModels, setLoadingModels] = useState(false); + // 检查 FFmpeg 可用性 useEffect(() => { if (isOpen) { @@ -58,10 +66,30 @@ export const MaterialImportDialog: React.FC = ({ if (isOpen) { setSelectedFiles([]); setStep('select'); + setSelectedModelId(''); clearError(); } }, [isOpen, clearError]); + // 加载可用模特 + useEffect(() => { + if (isOpen) { + loadAvailableModels(); + } + }, [isOpen]); + + const loadAvailableModels = async () => { + setLoadingModels(true); + try { + const models = await MaterialModelBindingService.getAllModels(); + setAvailableModels(models.filter(model => model.is_active)); + } catch (error) { + console.error('加载模特列表失败:', error); + } finally { + setLoadingModels(false); + } + }; + // 设置事件监听器用于接收导入进度更新 useEffect(() => { if (!isOpen) return; @@ -179,6 +207,7 @@ export const MaterialImportDialog: React.FC = ({ file_paths: selectedFiles, auto_process: autoProcess, max_segment_duration: maxSegmentDuration, + model_id: selectedModelId || undefined, }; console.log('开始异步导入:', request); @@ -390,6 +419,30 @@ export const MaterialImportDialog: React.FC = ({

)} + + {/* 模特绑定选择 */} +
+ + ({ + value: model.id, + label: model.stage_name || model.name, + })), + ]} + placeholder={loadingModels ? '加载中...' : '选择模特'} + disabled={loadingModels} + className="w-full" + /> +

+ 选择模特后,导入的素材将自动绑定到该模特 +

+
)} diff --git a/apps/desktop/src/types/material.ts b/apps/desktop/src/types/material.ts index f6e33fb..1271e2d 100644 --- a/apps/desktop/src/types/material.ts +++ b/apps/desktop/src/types/material.ts @@ -96,6 +96,7 @@ export interface CreateMaterialRequest { file_paths: string[]; auto_process: boolean; max_segment_duration?: number; + model_id?: string; // 可选的模特绑定ID } export interface MaterialProcessingConfig {