Files
mixvideo-v2/apps/desktop/src-tauri/src/presentation/commands/tvai_commands.rs
imeepos b7c16d70cf feat: 完整实现TVAI视频增强功能
主要功能:
- 集成Topaz Video AI SDK到桌面应用
- 支持视频放大、图片增强、视频插帧三种处理类型
- 完整的16个放大模型 + 4个插帧模型支持
- 用户友好的渐进式界面设计

 技术实现:
- Rust SDK: 完整的TVAI处理能力
- Tauri命令: 异步任务管理和进度跟踪
- React组件: 现代化UI和文件选择
- TypeScript服务: 类型安全的API调用

 用户体验:
- 步骤化引导 (选择类型  选择文件  处理设置)
- 智能预设系统 (老视频、游戏内容、动画、人像、通用)
- 原生文件对话框和自动路径生成
- 实时任务进度和状态管理

 修复:
- React Hooks调用顺序错误
- 插帧功能从占位符到完整实现
- 文件选择从手动输入到原生对话框

 文档:
- 完整的集成文档和使用指南
- 详细的功能总结和技术说明
2025-08-11 18:02:53 +08:00

873 lines
27 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use tauri::{State, AppHandle, Emitter};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
use uuid::Uuid;
use serde::{Deserialize, Serialize};
use tvai::{
TvaiProcessor, TvaiConfig, ProcessResult, TvaiError,
VideoUpscaleParams, InterpolationParams, VideoEnhanceParams,
ImageUpscaleParams, ImageFormat,
UpscaleModel, InterpolationModel, QualityPreset,
quick_upscale_video, quick_upscale_image, quick_interpolate_video, auto_enhance_video,
detect_topaz_installation, detect_gpu_support, detect_ffmpeg,
get_video_info, get_image_info, estimate_processing_time,
GpuInfo, FfmpegInfo, VideoInfo,
};
/// TVAI处理任务状态
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TvaiTaskStatus {
Pending,
Processing,
Completed,
Failed,
Cancelled,
}
/// TVAI处理任务
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TvaiTask {
pub id: String,
pub task_type: String,
pub input_path: String,
pub output_path: String,
pub status: TvaiTaskStatus,
pub progress: f32,
pub error_message: Option<String>,
pub created_at: chrono::DateTime<chrono::Utc>,
pub started_at: Option<chrono::DateTime<chrono::Utc>>,
pub completed_at: Option<chrono::DateTime<chrono::Utc>>,
pub processing_time: Option<u64>, // milliseconds
}
/// TVAI服务状态管理
pub struct TvaiState {
processor: Arc<Mutex<Option<TvaiProcessor>>>,
tasks: Arc<Mutex<HashMap<String, TvaiTask>>>,
config: Arc<Mutex<Option<TvaiConfig>>>,
}
impl TvaiState {
pub fn new() -> Self {
Self {
processor: Arc::new(Mutex::new(None)),
tasks: Arc::new(Mutex::new(HashMap::new())),
config: Arc::new(Mutex::new(None)),
}
}
}
/// 检测Topaz Video AI安装
#[tauri::command]
pub async fn detect_topaz_installation_command() -> Result<Option<String>, String> {
match detect_topaz_installation() {
Some(path) => Ok(Some(path.to_string_lossy().to_string())),
None => Ok(None),
}
}
/// 检测GPU支持
#[tauri::command]
pub async fn detect_gpu_support_command() -> Result<GpuInfo, String> {
Ok(detect_gpu_support())
}
/// 检测FFmpeg
#[tauri::command]
pub async fn detect_ffmpeg_command() -> Result<FfmpegInfo, String> {
Ok(detect_ffmpeg())
}
/// 初始化TVAI配置
#[tauri::command]
pub async fn initialize_tvai_config(
state: State<'_, TvaiState>,
topaz_path: Option<String>,
use_gpu: Option<bool>,
temp_dir: Option<String>,
) -> Result<(), String> {
let topaz_path = match topaz_path {
Some(path) => PathBuf::from(path),
None => detect_topaz_installation()
.ok_or("Topaz Video AI not found. Please specify the installation path.")?,
};
let mut config_builder = TvaiConfig::builder()
.topaz_path(topaz_path)
.use_gpu(use_gpu.unwrap_or(true));
if let Some(temp_dir) = temp_dir {
config_builder = config_builder.temp_dir(PathBuf::from(temp_dir));
}
let config = config_builder
.build()
.map_err(|e| format!("Failed to create TVAI config: {}", e))?;
let processor = TvaiProcessor::new(config.clone())
.map_err(|e| format!("Failed to create TVAI processor: {}", e))?;
{
let mut config_guard = state.config.lock().unwrap();
*config_guard = Some(config);
}
{
let mut processor_guard = state.processor.lock().unwrap();
*processor_guard = Some(processor);
}
Ok(())
}
/// 获取视频信息
#[tauri::command]
pub async fn get_video_info_command(
video_path: String,
) -> Result<serde_json::Value, String> {
let path = Path::new(&video_path);
let info = get_video_info(path).await
.map_err(|e| format!("Failed to get video info: {}", e))?;
let json_info = serde_json::json!({
"width": info.width,
"height": info.height,
"fps": info.fps,
"duration": info.duration.as_secs(),
"codec": info.codec,
"bitrate": info.bitrate,
"file_size": info.file_size,
});
Ok(json_info)
}
/// 获取图片信息
#[tauri::command]
pub async fn get_image_info_command(
image_path: String,
) -> Result<serde_json::Value, String> {
let path = Path::new(&image_path);
let info = get_image_info(path)
.map_err(|e| format!("Failed to get image info: {}", e))?;
let json_info = serde_json::json!({
"width": info.width,
"height": info.height,
"format": info.format,
"color_depth": info.color_depth,
"file_size": info.file_size,
});
Ok(json_info)
}
/// 估算处理时间
#[tauri::command]
pub async fn estimate_processing_time_command(
input_path: String,
scale_factor: Option<f32>,
) -> Result<u64, String> {
let path = Path::new(&input_path);
// 获取视频信息
let video_info = get_video_info(path).await
.map_err(|e| format!("Failed to get video info: {}", e))?;
// 创建默认的放大参数
let params = VideoUpscaleParams {
scale_factor: scale_factor.unwrap_or(2.0),
model: UpscaleModel::Iris3,
compression: 0.0,
blend: 0.0,
quality_preset: QualityPreset::HighQuality,
};
let time = estimate_processing_time(&video_info, &params);
Ok(time.as_secs())
}
/// 快速视频放大
#[tauri::command]
pub async fn quick_upscale_video_command(
app_handle: AppHandle,
state: State<'_, TvaiState>,
input_path: String,
output_path: String,
scale_factor: f32,
) -> Result<String, String> {
let task_id = Uuid::new_v4().to_string();
// 创建任务记录
let task = TvaiTask {
id: task_id.clone(),
task_type: "video_upscale".to_string(),
input_path: input_path.clone(),
output_path: output_path.clone(),
status: TvaiTaskStatus::Pending,
progress: 0.0,
error_message: None,
created_at: chrono::Utc::now(),
started_at: None,
completed_at: None,
processing_time: None,
};
{
let mut tasks = state.tasks.lock().unwrap();
tasks.insert(task_id.clone(), task);
}
// 发送任务创建事件
app_handle.emit("tvai_task_created", &task_id).unwrap();
// 异步处理
let tasks_clone = state.tasks.clone();
let app_handle_clone = app_handle.clone();
let task_id_clone = task_id.clone();
tokio::spawn(async move {
// 更新任务状态为处理中
{
let mut tasks = tasks_clone.lock().unwrap();
if let Some(task) = tasks.get_mut(&task_id_clone) {
task.status = TvaiTaskStatus::Processing;
task.started_at = Some(chrono::Utc::now());
}
}
app_handle_clone.emit("tvai_task_updated", &task_id_clone).unwrap();
let start_time = std::time::Instant::now();
// 执行处理
let result = quick_upscale_video(
Path::new(&input_path),
Path::new(&output_path),
scale_factor,
).await;
let processing_time = start_time.elapsed();
// 更新任务状态
{
let mut tasks = tasks_clone.lock().unwrap();
if let Some(task) = tasks.get_mut(&task_id_clone) {
match result {
Ok(_) => {
task.status = TvaiTaskStatus::Completed;
task.progress = 100.0;
task.completed_at = Some(chrono::Utc::now());
task.processing_time = Some(processing_time.as_millis() as u64);
}
Err(e) => {
task.status = TvaiTaskStatus::Failed;
task.error_message = Some(e.to_string());
task.completed_at = Some(chrono::Utc::now());
}
}
}
}
app_handle_clone.emit("tvai_task_updated", &task_id_clone).unwrap();
});
Ok(task_id)
}
/// 快速图片放大
#[tauri::command]
pub async fn quick_upscale_image_command(
app_handle: AppHandle,
state: State<'_, TvaiState>,
input_path: String,
output_path: String,
scale_factor: f32,
) -> Result<String, String> {
let task_id = Uuid::new_v4().to_string();
// 创建任务记录
let task = TvaiTask {
id: task_id.clone(),
task_type: "image_upscale".to_string(),
input_path: input_path.clone(),
output_path: output_path.clone(),
status: TvaiTaskStatus::Pending,
progress: 0.0,
error_message: None,
created_at: chrono::Utc::now(),
started_at: None,
completed_at: None,
processing_time: None,
};
{
let mut tasks = state.tasks.lock().unwrap();
tasks.insert(task_id.clone(), task);
}
// 发送任务创建事件
app_handle.emit("tvai_task_created", &task_id).unwrap();
// 异步处理
let tasks_clone = state.tasks.clone();
let app_handle_clone = app_handle.clone();
let task_id_clone = task_id.clone();
tokio::spawn(async move {
// 更新任务状态为处理中
{
let mut tasks = tasks_clone.lock().unwrap();
if let Some(task) = tasks.get_mut(&task_id_clone) {
task.status = TvaiTaskStatus::Processing;
task.started_at = Some(chrono::Utc::now());
}
}
app_handle_clone.emit("tvai_task_updated", &task_id_clone).unwrap();
let start_time = std::time::Instant::now();
// 执行处理
let result = quick_upscale_image(
Path::new(&input_path),
Path::new(&output_path),
scale_factor,
).await;
let processing_time = start_time.elapsed();
// 更新任务状态
{
let mut tasks = tasks_clone.lock().unwrap();
if let Some(task) = tasks.get_mut(&task_id_clone) {
match result {
Ok(_) => {
task.status = TvaiTaskStatus::Completed;
task.progress = 100.0;
task.completed_at = Some(chrono::Utc::now());
task.processing_time = Some(processing_time.as_millis() as u64);
}
Err(e) => {
task.status = TvaiTaskStatus::Failed;
task.error_message = Some(e.to_string());
task.completed_at = Some(chrono::Utc::now());
}
}
}
}
app_handle_clone.emit("tvai_task_updated", &task_id_clone).unwrap();
});
Ok(task_id)
}
/// 快速视频插帧
#[tauri::command]
pub async fn quick_interpolate_video_command(
app_handle: AppHandle,
state: State<'_, TvaiState>,
input_path: String,
output_path: String,
multiplier: f32,
input_fps: u32,
) -> Result<String, String> {
let task_id = Uuid::new_v4().to_string();
// 创建任务记录
let task = TvaiTask {
id: task_id.clone(),
task_type: "video_interpolation".to_string(),
input_path: input_path.clone(),
output_path: output_path.clone(),
status: TvaiTaskStatus::Pending,
progress: 0.0,
error_message: None,
created_at: chrono::Utc::now(),
started_at: None,
completed_at: None,
processing_time: None,
};
{
let mut tasks = state.tasks.lock().unwrap();
tasks.insert(task_id.clone(), task);
}
// 发送任务创建事件
app_handle.emit("tvai_task_created", &task_id).unwrap();
// 异步处理
let tasks_clone = state.tasks.clone();
let app_handle_clone = app_handle.clone();
let task_id_clone = task_id.clone();
tokio::spawn(async move {
// 更新任务状态为处理中
{
let mut tasks = tasks_clone.lock().unwrap();
if let Some(task) = tasks.get_mut(&task_id_clone) {
task.status = TvaiTaskStatus::Processing;
task.started_at = Some(chrono::Utc::now());
}
}
app_handle_clone.emit("tvai_task_updated", &task_id_clone).unwrap();
let start_time = std::time::Instant::now();
// 执行插帧处理
let result = quick_interpolate_video(
Path::new(&input_path),
Path::new(&output_path),
multiplier,
input_fps,
).await;
let processing_time = start_time.elapsed();
// 更新任务状态
{
let mut tasks = tasks_clone.lock().unwrap();
if let Some(task) = tasks.get_mut(&task_id_clone) {
match result {
Ok(_) => {
task.status = TvaiTaskStatus::Completed;
task.progress = 100.0;
task.completed_at = Some(chrono::Utc::now());
task.processing_time = Some(processing_time.as_millis() as u64);
}
Err(e) => {
task.status = TvaiTaskStatus::Failed;
task.error_message = Some(e.to_string());
task.completed_at = Some(chrono::Utc::now());
}
}
}
}
app_handle_clone.emit("tvai_task_updated", &task_id_clone).unwrap();
});
Ok(task_id)
}
/// 自动增强视频
#[tauri::command]
pub async fn auto_enhance_video_command(
app_handle: AppHandle,
state: State<'_, TvaiState>,
input_path: String,
output_path: String,
) -> Result<String, String> {
let task_id = Uuid::new_v4().to_string();
// 创建任务记录
let task = TvaiTask {
id: task_id.clone(),
task_type: "video_auto_enhance".to_string(),
input_path: input_path.clone(),
output_path: output_path.clone(),
status: TvaiTaskStatus::Pending,
progress: 0.0,
error_message: None,
created_at: chrono::Utc::now(),
started_at: None,
completed_at: None,
processing_time: None,
};
{
let mut tasks = state.tasks.lock().unwrap();
tasks.insert(task_id.clone(), task);
}
// 发送任务创建事件
app_handle.emit("tvai_task_created", &task_id).unwrap();
// 异步处理
let tasks_clone = state.tasks.clone();
let app_handle_clone = app_handle.clone();
let task_id_clone = task_id.clone();
tokio::spawn(async move {
// 更新任务状态为处理中
{
let mut tasks = tasks_clone.lock().unwrap();
if let Some(task) = tasks.get_mut(&task_id_clone) {
task.status = TvaiTaskStatus::Processing;
task.started_at = Some(chrono::Utc::now());
}
}
app_handle_clone.emit("tvai_task_updated", &task_id_clone).unwrap();
let start_time = std::time::Instant::now();
// 执行处理
let result = auto_enhance_video(
Path::new(&input_path),
Path::new(&output_path),
).await;
let processing_time = start_time.elapsed();
// 更新任务状态
{
let mut tasks = tasks_clone.lock().unwrap();
if let Some(task) = tasks.get_mut(&task_id_clone) {
match result {
Ok(_) => {
task.status = TvaiTaskStatus::Completed;
task.progress = 100.0;
task.completed_at = Some(chrono::Utc::now());
task.processing_time = Some(processing_time.as_millis() as u64);
}
Err(e) => {
task.status = TvaiTaskStatus::Failed;
task.error_message = Some(e.to_string());
task.completed_at = Some(chrono::Utc::now());
}
}
}
}
app_handle_clone.emit("tvai_task_updated", &task_id_clone).unwrap();
});
Ok(task_id)
}
/// 自动增强图片(使用快速放大作为替代)
#[tauri::command]
pub async fn auto_enhance_image_command(
app_handle: AppHandle,
state: State<'_, TvaiState>,
input_path: String,
output_path: String,
) -> Result<String, String> {
// 使用快速图片放大作为自动增强的替代
quick_upscale_image_command(app_handle, state, input_path, output_path, 2.0).await
}
/// 高级视频放大(支持自定义参数)
#[tauri::command]
pub async fn upscale_video_advanced(
app_handle: AppHandle,
state: State<'_, TvaiState>,
input_path: String,
output_path: String,
scale_factor: f32,
model: String,
compression: f32,
blend: f32,
quality_preset: String,
) -> Result<String, String> {
let task_id = Uuid::new_v4().to_string();
// 解析模型
let upscale_model = match model.as_str() {
"aaa-9" => UpscaleModel::Aaa9,
"ahq-12" => UpscaleModel::Ahq12,
"alq-13" => UpscaleModel::Alq13,
"alqs-2" => UpscaleModel::Alqs2,
"amq-13" => UpscaleModel::Amq13,
"amqs-2" => UpscaleModel::Amqs2,
"ghq-5" => UpscaleModel::Ghq5,
"iris-2" => UpscaleModel::Iris2,
"iris-3" => UpscaleModel::Iris3,
"nyx-3" => UpscaleModel::Nyx3,
"prob-4" => UpscaleModel::Prob4,
"thf-4" => UpscaleModel::Thf4,
"thd-3" => UpscaleModel::Thd3,
"thm-2" => UpscaleModel::Thm2,
"rhea-1" => UpscaleModel::Rhea1,
"rxl-1" => UpscaleModel::Rxl1,
_ => return Err(format!("Unsupported upscale model: {}", model)),
};
// 解析质量预设
let preset = match quality_preset.as_str() {
"fast" => QualityPreset::Fast,
"balanced" => QualityPreset::Balanced,
"high_quality" => QualityPreset::HighQuality,
"maximum" => QualityPreset::Maximum,
_ => return Err(format!("Unsupported quality preset: {}", quality_preset)),
};
// 创建任务记录
let task = TvaiTask {
id: task_id.clone(),
task_type: "video_upscale_advanced".to_string(),
input_path: input_path.clone(),
output_path: output_path.clone(),
status: TvaiTaskStatus::Pending,
progress: 0.0,
error_message: None,
created_at: chrono::Utc::now(),
started_at: None,
completed_at: None,
processing_time: None,
};
{
let mut tasks = state.tasks.lock().unwrap();
tasks.insert(task_id.clone(), task);
}
// 发送任务创建事件
app_handle.emit("tvai_task_created", &task_id).unwrap();
// 异步处理
let processor_clone = state.processor.clone();
let tasks_clone = state.tasks.clone();
let app_handle_clone = app_handle.clone();
let task_id_clone = task_id.clone();
tokio::spawn(async move {
// 更新任务状态为处理中
{
let mut tasks = tasks_clone.lock().unwrap();
if let Some(task) = tasks.get_mut(&task_id_clone) {
task.status = TvaiTaskStatus::Processing;
task.started_at = Some(chrono::Utc::now());
}
}
app_handle_clone.emit("tvai_task_updated", &task_id_clone).unwrap();
let start_time = std::time::Instant::now();
// 执行处理
let result: Result<ProcessResult, TvaiError> = {
let processor_guard = processor_clone.lock().unwrap();
if let Some(ref mut processor) = processor_guard.as_ref() {
let params = VideoUpscaleParams {
scale_factor,
model: upscale_model,
compression,
blend,
quality_preset: preset,
};
// 注意这里需要克隆processor因为我们不能在异步块中持有锁
// 实际实现中可能需要重新设计架构
Err(TvaiError::InvalidParameter("Processor architecture needs redesign for async usage".to_string()))
} else {
Err(TvaiError::ConfigurationError("TVAI processor not initialized".to_string()))
}
};
let processing_time = start_time.elapsed();
// 更新任务状态
{
let mut tasks = tasks_clone.lock().unwrap();
if let Some(task) = tasks.get_mut(&task_id_clone) {
match result {
Ok(_) => {
task.status = TvaiTaskStatus::Completed;
task.progress = 100.0;
task.completed_at = Some(chrono::Utc::now());
task.processing_time = Some(processing_time.as_millis() as u64);
}
Err(e) => {
task.status = TvaiTaskStatus::Failed;
task.error_message = Some(e.to_string());
task.completed_at = Some(chrono::Utc::now());
}
}
}
}
app_handle_clone.emit("tvai_task_updated", &task_id_clone).unwrap();
});
Ok(task_id)
}
/// 高级图片放大(支持自定义参数)
#[tauri::command]
pub async fn upscale_image_advanced(
app_handle: AppHandle,
state: State<'_, TvaiState>,
input_path: String,
output_path: String,
scale_factor: f32,
model: String,
compression: f32,
blend: f32,
output_format: String,
) -> Result<String, String> {
let task_id = Uuid::new_v4().to_string();
// 解析模型
let upscale_model = match model.as_str() {
"aaa-9" => UpscaleModel::Aaa9,
"ahq-12" => UpscaleModel::Ahq12,
"alq-13" => UpscaleModel::Alq13,
"alqs-2" => UpscaleModel::Alqs2,
"amq-13" => UpscaleModel::Amq13,
"amqs-2" => UpscaleModel::Amqs2,
"ghq-5" => UpscaleModel::Ghq5,
"iris-2" => UpscaleModel::Iris2,
"iris-3" => UpscaleModel::Iris3,
"nyx-3" => UpscaleModel::Nyx3,
"prob-4" => UpscaleModel::Prob4,
"thf-4" => UpscaleModel::Thf4,
"thd-3" => UpscaleModel::Thd3,
"thm-2" => UpscaleModel::Thm2,
"rhea-1" => UpscaleModel::Rhea1,
"rxl-1" => UpscaleModel::Rxl1,
_ => return Err(format!("Unsupported upscale model: {}", model)),
};
// 解析输出格式
let format = match output_format.as_str() {
"png" => ImageFormat::Png,
"jpg" | "jpeg" => ImageFormat::Jpg,
"tiff" => ImageFormat::Tiff,
"bmp" => ImageFormat::Bmp,
_ => return Err(format!("Unsupported output format: {}", output_format)),
};
// 创建任务记录
let task = TvaiTask {
id: task_id.clone(),
task_type: "image_upscale_advanced".to_string(),
input_path: input_path.clone(),
output_path: output_path.clone(),
status: TvaiTaskStatus::Pending,
progress: 0.0,
error_message: None,
created_at: chrono::Utc::now(),
started_at: None,
completed_at: None,
processing_time: None,
};
{
let mut tasks = state.tasks.lock().unwrap();
tasks.insert(task_id.clone(), task);
}
// 发送任务创建事件
app_handle.emit("tvai_task_created", &task_id).unwrap();
// 异步处理(使用快速函数,因为高级处理器需要重新设计架构)
let tasks_clone = state.tasks.clone();
let app_handle_clone = app_handle.clone();
let task_id_clone = task_id.clone();
tokio::spawn(async move {
// 更新任务状态为处理中
{
let mut tasks = tasks_clone.lock().unwrap();
if let Some(task) = tasks.get_mut(&task_id_clone) {
task.status = TvaiTaskStatus::Processing;
task.started_at = Some(chrono::Utc::now());
}
}
app_handle_clone.emit("tvai_task_updated", &task_id_clone).unwrap();
let start_time = std::time::Instant::now();
// 执行处理(暂时使用快速函数)
let result = quick_upscale_image(
Path::new(&input_path),
Path::new(&output_path),
scale_factor,
).await;
let processing_time = start_time.elapsed();
// 更新任务状态
{
let mut tasks = tasks_clone.lock().unwrap();
if let Some(task) = tasks.get_mut(&task_id_clone) {
match result {
Ok(_) => {
task.status = TvaiTaskStatus::Completed;
task.progress = 100.0;
task.completed_at = Some(chrono::Utc::now());
task.processing_time = Some(processing_time.as_millis() as u64);
}
Err(e) => {
task.status = TvaiTaskStatus::Failed;
task.error_message = Some(e.to_string());
task.completed_at = Some(chrono::Utc::now());
}
}
}
}
app_handle_clone.emit("tvai_task_updated", &task_id_clone).unwrap();
});
Ok(task_id)
}
/// 获取任务状态
#[tauri::command]
pub async fn get_tvai_task_status(
state: State<'_, TvaiState>,
task_id: String,
) -> Result<Option<TvaiTask>, String> {
let tasks = state.tasks.lock().unwrap();
Ok(tasks.get(&task_id).cloned())
}
/// 获取所有任务
#[tauri::command]
pub async fn get_all_tvai_tasks(
state: State<'_, TvaiState>,
) -> Result<Vec<TvaiTask>, String> {
let tasks = state.tasks.lock().unwrap();
Ok(tasks.values().cloned().collect())
}
/// 取消任务
#[tauri::command]
pub async fn cancel_tvai_task(
app_handle: AppHandle,
state: State<'_, TvaiState>,
task_id: String,
) -> Result<(), String> {
{
let mut tasks = state.tasks.lock().unwrap();
if let Some(task) = tasks.get_mut(&task_id) {
if matches!(task.status, TvaiTaskStatus::Pending | TvaiTaskStatus::Processing) {
task.status = TvaiTaskStatus::Cancelled;
task.completed_at = Some(chrono::Utc::now());
} else {
return Err("Task cannot be cancelled in current state".to_string());
}
} else {
return Err("Task not found".to_string());
}
}
app_handle.emit("tvai_task_updated", &task_id).unwrap();
Ok(())
}
/// 清理已完成的任务
#[tauri::command]
pub async fn cleanup_completed_tvai_tasks(
state: State<'_, TvaiState>,
) -> Result<usize, String> {
let mut tasks = state.tasks.lock().unwrap();
let initial_count = tasks.len();
tasks.retain(|_, task| {
!matches!(task.status, TvaiTaskStatus::Completed | TvaiTaskStatus::Failed | TvaiTaskStatus::Cancelled)
});
let removed_count = initial_count - tasks.len();
Ok(removed_count)
}