diff --git a/apps/desktop/src-tauri/src/business/services/video_classification_queue.rs b/apps/desktop/src-tauri/src/business/services/video_classification_queue.rs index 9b5785d..aca5034 100644 --- a/apps/desktop/src-tauri/src/business/services/video_classification_queue.rs +++ b/apps/desktop/src-tauri/src/business/services/video_classification_queue.rs @@ -85,6 +85,18 @@ impl VideoClassificationQueue { *status = QueueStatus::Running; drop(status); + // 恢复卡住的任务状态 + match self.service.recover_stuck_tasks().await { + Ok(recovered_count) => { + if recovered_count > 0 { + println!("🔄 队列启动时恢复了 {} 个卡住的任务", recovered_count); + } + } + Err(e) => { + println!("⚠️ 恢复卡住任务时出错: {}", e); + } + } + // 更新统计信息 self.update_stats().await?; diff --git a/apps/desktop/src-tauri/src/business/services/video_classification_service.rs b/apps/desktop/src-tauri/src/business/services/video_classification_service.rs index 37fb339..06f669e 100644 --- a/apps/desktop/src-tauri/src/business/services/video_classification_service.rs +++ b/apps/desktop/src-tauri/src/business/services/video_classification_service.rs @@ -305,6 +305,11 @@ impl VideoClassificationService { self.video_repo.delete_task(task_id).await } + /// 恢复卡住的任务状态 + pub async fn recover_stuck_tasks(&self) -> Result { + self.video_repo.recover_stuck_tasks().await + } + /// 重试失败的任务 pub async fn retry_failed_task(&self, _task_id: &str) -> Result<()> { // 这里需要实现重试逻辑 diff --git a/apps/desktop/src-tauri/src/data/repositories/video_classification_repository.rs b/apps/desktop/src-tauri/src/data/repositories/video_classification_repository.rs index ae1c2b9..407d6ab 100644 --- a/apps/desktop/src-tauri/src/data/repositories/video_classification_repository.rs +++ b/apps/desktop/src-tauri/src/data/repositories/video_classification_repository.rs @@ -472,6 +472,25 @@ impl VideoClassificationRepository { let completed_json = serde_json::to_string(&TaskStatus::Completed)?; let failed_json = serde_json::to_string(&TaskStatus::Failed)?; + // 调试:查看数据库中实际的任务状态分布 + let debug_query = if let Some(project_id) = project_id { + format!("SELECT status, COUNT(*) FROM video_classification_tasks WHERE project_id = '{}' GROUP BY status", project_id) + } else { + "SELECT status, COUNT(*) FROM video_classification_tasks GROUP BY status".to_string() + }; + + let mut debug_stmt = conn.prepare(&debug_query)?; + let debug_rows = debug_stmt.query_map([], |row| { + Ok((row.get::<_, String>(0)?, row.get::<_, i32>(1)?)) + })?; + + println!("🔍 数据库中实际的任务状态分布:"); + for row in debug_rows { + if let Ok((status, count)) = row { + println!(" 状态: {} -> 数量: {}", status, count); + } + } + // 获取任务统计 let mut stmt = conn.prepare(&format!( "SELECT @@ -484,13 +503,12 @@ impl VideoClassificationRepository { ))?; let task_stats = stmt.query_row([&pending_json, &uploading_json, &analyzing_json, &completed_json, &failed_json], |row| { - Ok(( - row.get::<_, i32>(0)?, - row.get::<_, i32>(1)?, - row.get::<_, i32>(2)?, - row.get::<_, i32>(3)?, - row.get::<_, i32>(4)?, - )) + let total: i32 = row.get(0)?; + let pending: i32 = row.get(1)?; + let processing: i32 = row.get(2)?; + let completed: i32 = row.get(3)?; + let failed: i32 = row.get(4)?; + Ok((total, pending, processing, completed, failed)) })?; // 获取分类记录统计 @@ -544,6 +562,49 @@ impl VideoClassificationRepository { Ok(()) } + /// 恢复卡住的任务状态 + /// 将所有处理中的任务(Uploading, Analyzing)重置为Pending状态 + pub async fn recover_stuck_tasks(&self) -> Result { + let conn = self.database.get_connection(); + let conn = conn.lock().unwrap(); + + let uploading_json = serde_json::to_string(&TaskStatus::Uploading)?; + let analyzing_json = serde_json::to_string(&TaskStatus::Analyzing)?; + let pending_json = serde_json::to_string(&TaskStatus::Pending)?; + + println!("🔄 开始恢复卡住的任务状态..."); + + // 查询卡住的任务 + let mut stmt = conn.prepare( + "SELECT id, status FROM video_classification_tasks WHERE status = ? OR status = ?" + )?; + + let stuck_tasks: Vec<(String, String)> = stmt.query_map([&uploading_json, &analyzing_json], |row| { + Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)) + })?.collect::, _>>()?; + + if stuck_tasks.is_empty() { + println!("✅ 没有发现卡住的任务"); + return Ok(0); + } + + println!("🔍 发现 {} 个卡住的任务:", stuck_tasks.len()); + for (id, status) in &stuck_tasks { + println!(" 任务ID: {}, 状态: {}", &id[..8], status); + } + + // 重置任务状态 + let updated = conn.execute( + "UPDATE video_classification_tasks + SET status = ?, started_at = NULL, updated_at = datetime('now') + WHERE status = ? OR status = ?", + [&pending_json, &uploading_json, &analyzing_json] + )?; + + println!("✅ 已恢复 {} 个任务状态为Pending", updated); + Ok(updated) + } + /// 根据ID获取分类任务 pub async fn get_task_by_id(&self, task_id: &str) -> Result> { let conn = self.database.get_connection(); diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 4878d02..7c84db3 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -112,6 +112,7 @@ pub fn run() { commands::video_classification_commands::get_all_classification_task_progress, commands::video_classification_commands::get_project_classification_task_progress, commands::video_classification_commands::stop_classification_queue, + commands::video_classification_commands::recover_stuck_classification_tasks, commands::video_classification_commands::pause_classification_queue, commands::video_classification_commands::resume_classification_queue, commands::video_classification_commands::get_material_classification_records, diff --git a/apps/desktop/src-tauri/src/presentation/commands/video_classification_commands.rs b/apps/desktop/src-tauri/src/presentation/commands/video_classification_commands.rs index bfbcb05..23453af 100644 --- a/apps/desktop/src-tauri/src/presentation/commands/video_classification_commands.rs +++ b/apps/desktop/src-tauri/src/presentation/commands/video_classification_commands.rs @@ -118,6 +118,28 @@ pub async fn stop_classification_queue( queue.stop().await.map_err(|e| e.to_string()) } +/// 恢复卡住的任务状态 +#[command] +pub async fn recover_stuck_classification_tasks( + state: State<'_, AppState>, +) -> Result { + let database = state.get_database(); + let video_repo = Arc::new(VideoClassificationRepository::new(database.clone())); + let ai_classification_repo = Arc::new(AiClassificationRepository::new(database.clone())); + let material_repo = Arc::new(MaterialRepository::new(database.get_connection()).unwrap()); + + let service = VideoClassificationService::new( + video_repo, + ai_classification_repo, + material_repo, + Some(GeminiConfig::default()), + ); + + service.recover_stuck_tasks() + .await + .map_err(|e| e.to_string()) +} + /// 暂停分类队列 #[command] pub async fn pause_classification_queue( diff --git a/apps/desktop/src/components/Navigation.tsx b/apps/desktop/src/components/Navigation.tsx index c1ea7aa..8a360c0 100644 --- a/apps/desktop/src/components/Navigation.tsx +++ b/apps/desktop/src/components/Navigation.tsx @@ -60,7 +60,7 @@ const Navigation: React.FC = () => { return (