- 将 Hedra 口型合成任务改为异步处理模式 - 添加完整的数据模型和仓储层支持 - 实现后台任务轮询和实时进度通知 - 创建 HedraLipSyncRecords 页面显示任务列表 - 将原有功能封装为 Modal 组件 - 支持多任务并发处理和状态跟踪 - 添加事件驱动的前端状态更新机制 主要变更: - 新增 HedraLipSyncRecord 数据模型 - 新增 HedraLipSyncRepository 仓储层 - 新增 HedraLipSyncModal 组件 - 新增 HedraLipSyncRecords 页面 - 修改 bowong_text_video_agent_commands 支持异步处理 - 添加事件总线支持 Hedra 任务进度通知 - 更新路由配置和工具列表
269 lines
9.2 KiB
Rust
269 lines
9.2 KiB
Rust
use std::collections::HashMap;
|
|
use std::sync::{Arc, Mutex};
|
|
use std::time::Duration;
|
|
use anyhow::Result;
|
|
use tokio::time::sleep;
|
|
use tracing::{info, debug, error};
|
|
|
|
use crate::data::models::hedra_lipsync_record::HedraLipSyncStatus;
|
|
use crate::data::repositories::hedra_lipsync_repository::HedraLipSyncRepository;
|
|
use crate::infrastructure::bowong_text_video_agent_service::BowongTextVideoAgentService;
|
|
use crate::infrastructure::event_bus::EventBusManager;
|
|
use crate::data::models::bowong_text_video_agent::HedraTaskStatusParams;
|
|
|
|
/// Hedra 任务轮询器
|
|
/// 负责在后台轮询 Hedra 任务状态并发送事件通知
|
|
pub struct HedraTaskPoller {
|
|
/// 正在轮询的任务
|
|
polling_tasks: Arc<Mutex<HashMap<String, PollingTask>>>,
|
|
/// BowongTextVideoAgent 服务
|
|
service: Arc<BowongTextVideoAgentService>,
|
|
/// 事件总线管理器
|
|
event_bus: Arc<EventBusManager>,
|
|
/// 数据库仓库
|
|
repository: Arc<HedraLipSyncRepository>,
|
|
/// 是否正在运行
|
|
is_running: Arc<Mutex<bool>>,
|
|
}
|
|
|
|
/// 轮询任务信息
|
|
#[derive(Debug, Clone)]
|
|
struct PollingTask {
|
|
task_id: String,
|
|
record_id: String,
|
|
start_time: std::time::Instant,
|
|
poll_count: u32,
|
|
max_polls: u32,
|
|
}
|
|
|
|
impl HedraTaskPoller {
|
|
/// 创建新的 Hedra 任务轮询器
|
|
pub fn new(
|
|
service: Arc<BowongTextVideoAgentService>,
|
|
event_bus: Arc<EventBusManager>,
|
|
repository: Arc<HedraLipSyncRepository>,
|
|
) -> Self {
|
|
Self {
|
|
polling_tasks: Arc::new(Mutex::new(HashMap::new())),
|
|
service,
|
|
event_bus,
|
|
repository,
|
|
is_running: Arc::new(Mutex::new(false)),
|
|
}
|
|
}
|
|
|
|
/// 添加任务到轮询队列
|
|
pub fn add_task(&self, task_id: String, record_id: String) -> Result<()> {
|
|
let mut tasks = self.polling_tasks.lock().unwrap();
|
|
|
|
let polling_task = PollingTask {
|
|
task_id: task_id.clone(),
|
|
record_id: record_id.clone(),
|
|
start_time: std::time::Instant::now(),
|
|
poll_count: 0,
|
|
max_polls: 360, // 30分钟 (5秒间隔 * 360次)
|
|
};
|
|
|
|
tasks.insert(task_id.clone(), polling_task);
|
|
info!("添加 Hedra 任务到轮询队列: task_id={}, record_id={}", task_id, record_id);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// 移除轮询任务
|
|
pub fn remove_task(&self, task_id: &str) {
|
|
let mut tasks = self.polling_tasks.lock().unwrap();
|
|
if tasks.remove(task_id).is_some() {
|
|
debug!("从轮询队列移除任务: {}", task_id);
|
|
}
|
|
}
|
|
|
|
/// 启动轮询器
|
|
pub async fn start(&self) -> Result<()> {
|
|
{
|
|
let mut running = self.is_running.lock().unwrap();
|
|
if *running {
|
|
return Ok(());
|
|
}
|
|
*running = true;
|
|
}
|
|
|
|
info!("启动 Hedra 任务轮询器");
|
|
|
|
// 启动轮询循环
|
|
let polling_tasks = Arc::clone(&self.polling_tasks);
|
|
let service = Arc::clone(&self.service);
|
|
let event_bus = Arc::clone(&self.event_bus);
|
|
let repository = Arc::clone(&self.repository);
|
|
let is_running = Arc::clone(&self.is_running);
|
|
|
|
tokio::spawn(async move {
|
|
while *is_running.lock().unwrap() {
|
|
// 获取当前需要轮询的任务
|
|
let tasks_to_poll: Vec<PollingTask> = {
|
|
let tasks = polling_tasks.lock().unwrap();
|
|
tasks.values().cloned().collect()
|
|
};
|
|
|
|
// 轮询每个任务
|
|
for mut task in tasks_to_poll {
|
|
if let Err(e) = Self::poll_single_task(
|
|
&mut task,
|
|
&service,
|
|
&event_bus,
|
|
&repository,
|
|
&polling_tasks,
|
|
).await {
|
|
error!("轮询任务 {} 时出错: {}", task.task_id, e);
|
|
}
|
|
}
|
|
|
|
// 等待 5 秒后继续下一轮轮询
|
|
sleep(Duration::from_secs(5)).await;
|
|
}
|
|
|
|
info!("Hedra 任务轮询器已停止");
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// 停止轮询器
|
|
pub fn stop(&self) {
|
|
let mut running = self.is_running.lock().unwrap();
|
|
*running = false;
|
|
info!("停止 Hedra 任务轮询器");
|
|
}
|
|
|
|
/// 轮询单个任务
|
|
async fn poll_single_task(
|
|
task: &mut PollingTask,
|
|
service: &BowongTextVideoAgentService,
|
|
event_bus: &EventBusManager,
|
|
repository: &HedraLipSyncRepository,
|
|
polling_tasks: &Arc<Mutex<HashMap<String, PollingTask>>>,
|
|
) -> Result<()> {
|
|
task.poll_count += 1;
|
|
|
|
debug!("轮询 Hedra 任务: {} (第 {} 次)", task.task_id, task.poll_count);
|
|
|
|
// 检查是否超过最大轮询次数
|
|
if task.poll_count > task.max_polls {
|
|
error!("任务 {} 轮询超时,移除任务", task.task_id);
|
|
|
|
// 发送超时事件
|
|
let _ = event_bus.publish_hedra_task_progress(
|
|
task.task_id.clone(),
|
|
task.record_id.clone(),
|
|
"failed".to_string(),
|
|
None,
|
|
None,
|
|
Some("任务轮询超时".to_string()),
|
|
).await;
|
|
|
|
// 更新数据库状态
|
|
let _ = repository.update_status_and_error(
|
|
&task.record_id,
|
|
HedraLipSyncStatus::Failed,
|
|
Some("任务轮询超时".to_string()),
|
|
);
|
|
|
|
// 从轮询队列移除
|
|
polling_tasks.lock().unwrap().remove(&task.task_id);
|
|
return Ok(());
|
|
}
|
|
|
|
// 查询任务状态
|
|
let status_params = HedraTaskStatusParams {
|
|
task_id: task.task_id.clone(),
|
|
};
|
|
|
|
match service.hedra_query_task_status(&status_params).await {
|
|
Ok(status_response) => {
|
|
debug!("任务 {} 状态: {}", task.task_id, status_response.status);
|
|
|
|
// 映射状态
|
|
let mapped_status = match status_response.status.as_str() {
|
|
"success" => "completed",
|
|
"running" => "processing",
|
|
"failed" => "failed",
|
|
"cancelled" => "cancelled",
|
|
"pending" | _ => "pending",
|
|
};
|
|
|
|
// 提取视频URL
|
|
let video_url = if status_response.status == "success" {
|
|
status_response.result
|
|
.as_ref()
|
|
.and_then(|result| {
|
|
result.get("video_url")
|
|
.or_else(|| result.get("url"))
|
|
.or_else(|| result.get("video"))
|
|
.and_then(|v| v.as_str())
|
|
.map(|s| s.to_string())
|
|
})
|
|
} else {
|
|
None
|
|
};
|
|
|
|
// 发送进度事件
|
|
let _ = event_bus.publish_hedra_task_progress(
|
|
task.task_id.clone(),
|
|
task.record_id.clone(),
|
|
mapped_status.to_string(),
|
|
status_response.progress.map(|p| p as f64),
|
|
video_url.clone(),
|
|
status_response.error.clone(),
|
|
).await;
|
|
|
|
// 更新数据库
|
|
let db_status = match mapped_status {
|
|
"completed" => HedraLipSyncStatus::Completed,
|
|
"processing" => HedraLipSyncStatus::Processing,
|
|
"failed" => HedraLipSyncStatus::Failed,
|
|
"cancelled" => HedraLipSyncStatus::Cancelled,
|
|
_ => HedraLipSyncStatus::Pending,
|
|
};
|
|
|
|
let _ = repository.update_progress_and_result(
|
|
&task.record_id,
|
|
db_status,
|
|
status_response.progress.unwrap_or(0.0) as i32,
|
|
video_url,
|
|
status_response.error,
|
|
);
|
|
|
|
// 如果任务完成或失败,从轮询队列移除
|
|
if matches!(mapped_status, "completed" | "failed" | "cancelled") {
|
|
info!("任务 {} 已完成,状态: {}", task.task_id, mapped_status);
|
|
polling_tasks.lock().unwrap().remove(&task.task_id);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
error!("查询任务 {} 状态失败: {}", task.task_id, e);
|
|
|
|
// 如果连续查询失败次数过多,标记为失败
|
|
if task.poll_count > 10 {
|
|
let _ = event_bus.publish_hedra_task_progress(
|
|
task.task_id.clone(),
|
|
task.record_id.clone(),
|
|
"failed".to_string(),
|
|
None,
|
|
None,
|
|
Some(format!("状态查询失败: {}", e)),
|
|
).await;
|
|
|
|
polling_tasks.lock().unwrap().remove(&task.task_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// 获取当前轮询任务数量
|
|
pub fn get_polling_task_count(&self) -> usize {
|
|
self.polling_tasks.lock().unwrap().len()
|
|
}
|
|
}
|