feat: 基础架构重构
This commit is contained in:
278
apps/desktop/src-tauri/src/business/services/comfyui_manager.rs
Normal file
278
apps/desktop/src-tauri/src/business/services/comfyui_manager.rs
Normal file
@@ -0,0 +1,278 @@
|
||||
//! ComfyUI 管理器
|
||||
//! 统一的 SDK 管理器,负责连接管理、健康检查和配置管理
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{info, warn, error, debug};
|
||||
|
||||
use comfyui_sdk::client::ComfyUIClient;
|
||||
use comfyui_sdk::types::{ComfyUIClientConfig, SystemStats, QueueStatus, ObjectInfo};
|
||||
|
||||
use crate::data::models::comfyui::{ComfyUIConfig, ValidationResult};
|
||||
|
||||
/// ComfyUI 连接状态
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ConnectionStatus {
|
||||
/// 未连接
|
||||
Disconnected,
|
||||
/// 连接中
|
||||
Connecting,
|
||||
/// 已连接
|
||||
Connected,
|
||||
/// 连接失败
|
||||
Failed(String),
|
||||
}
|
||||
|
||||
/// ComfyUI 管理器
|
||||
/// 提供统一的 ComfyUI 服务管理接口
|
||||
pub struct ComfyUIManager {
|
||||
/// SDK 客户端
|
||||
client: Arc<RwLock<Option<ComfyUIClient>>>,
|
||||
/// 配置信息
|
||||
config: Arc<RwLock<ComfyUIConfig>>,
|
||||
/// 连接状态
|
||||
status: Arc<RwLock<ConnectionStatus>>,
|
||||
/// 最后健康检查时间
|
||||
last_health_check: Arc<RwLock<Option<std::time::Instant>>>,
|
||||
}
|
||||
|
||||
impl ComfyUIManager {
|
||||
/// 创建新的 ComfyUI 管理器
|
||||
pub fn new(config: ComfyUIConfig) -> Result<Self> {
|
||||
// 验证配置
|
||||
let validation = config.validate();
|
||||
if !validation.valid {
|
||||
return Err(anyhow!("配置验证失败: {:?}", validation.errors));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
client: Arc::new(RwLock::new(None)),
|
||||
config: Arc::new(RwLock::new(config)),
|
||||
status: Arc::new(RwLock::new(ConnectionStatus::Disconnected)),
|
||||
last_health_check: Arc::new(RwLock::new(None)),
|
||||
})
|
||||
}
|
||||
|
||||
/// 连接到 ComfyUI 服务
|
||||
pub async fn connect(&self) -> Result<()> {
|
||||
info!("开始连接 ComfyUI 服务");
|
||||
|
||||
// 更新状态为连接中
|
||||
*self.status.write().await = ConnectionStatus::Connecting;
|
||||
|
||||
let config = self.config.read().await;
|
||||
let sdk_config = config.to_sdk_config();
|
||||
|
||||
match ComfyUIClient::new(sdk_config) {
|
||||
Ok(client) => {
|
||||
// 测试连接
|
||||
match client.get_system_stats().await {
|
||||
Ok(stats) => {
|
||||
*self.client.write().await = Some(client);
|
||||
*self.status.write().await = ConnectionStatus::Connected;
|
||||
*self.last_health_check.write().await = Some(std::time::Instant::now());
|
||||
|
||||
info!("ComfyUI 连接成功,系统信息: OS={}, Python={}",
|
||||
stats.system.os, stats.system.python_version);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
let error_msg = format!("连接测试失败: {}", e);
|
||||
*self.status.write().await = ConnectionStatus::Failed(error_msg.clone());
|
||||
Err(anyhow!(error_msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let error_msg = format!("创建客户端失败: {}", e);
|
||||
*self.status.write().await = ConnectionStatus::Failed(error_msg.clone());
|
||||
Err(anyhow!(error_msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 断开连接
|
||||
pub async fn disconnect(&self) -> Result<()> {
|
||||
info!("断开 ComfyUI 连接");
|
||||
|
||||
*self.client.write().await = None;
|
||||
*self.status.write().await = ConnectionStatus::Disconnected;
|
||||
*self.last_health_check.write().await = None;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 检查是否已连接
|
||||
pub async fn is_connected(&self) -> bool {
|
||||
matches!(*self.status.read().await, ConnectionStatus::Connected)
|
||||
}
|
||||
|
||||
/// 获取连接状态
|
||||
pub async fn get_connection_status(&self) -> ConnectionStatus {
|
||||
self.status.read().await.clone()
|
||||
}
|
||||
|
||||
/// 健康检查
|
||||
pub async fn health_check(&self) -> Result<bool> {
|
||||
let client_guard = self.client.read().await;
|
||||
let client = match client_guard.as_ref() {
|
||||
Some(client) => client,
|
||||
None => {
|
||||
warn!("客户端未连接,健康检查失败");
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
|
||||
match client.get_system_stats().await {
|
||||
Ok(_) => {
|
||||
*self.last_health_check.write().await = Some(std::time::Instant::now());
|
||||
debug!("健康检查通过");
|
||||
Ok(true)
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("健康检查失败: {}", e);
|
||||
// 如果健康检查失败,更新连接状态
|
||||
*self.status.write().await = ConnectionStatus::Failed(format!("健康检查失败: {}", e));
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取系统信息
|
||||
pub async fn get_system_info(&self) -> Result<SystemStats> {
|
||||
let client_guard = self.client.read().await;
|
||||
let client = match client_guard.as_ref() {
|
||||
Some(client) => client,
|
||||
None => return Err(anyhow!("客户端未连接")),
|
||||
};
|
||||
|
||||
client.get_system_stats().await
|
||||
.map_err(|e| anyhow!("获取系统信息失败: {}", e))
|
||||
}
|
||||
|
||||
/// 获取队列状态
|
||||
pub async fn get_queue_status(&self) -> Result<QueueStatus> {
|
||||
let client_guard = self.client.read().await;
|
||||
let client = match client_guard.as_ref() {
|
||||
Some(client) => client,
|
||||
None => return Err(anyhow!("客户端未连接")),
|
||||
};
|
||||
|
||||
client.get_queue().await
|
||||
.map_err(|e| anyhow!("获取队列状态失败: {}", e))
|
||||
}
|
||||
|
||||
/// 获取对象信息
|
||||
pub async fn get_object_info(&self) -> Result<ObjectInfo> {
|
||||
let client_guard = self.client.read().await;
|
||||
let client = match client_guard.as_ref() {
|
||||
Some(client) => client,
|
||||
None => return Err(anyhow!("客户端未连接")),
|
||||
};
|
||||
|
||||
client.get_object_info().await
|
||||
.map_err(|e| anyhow!("获取对象信息失败: {}", e))
|
||||
}
|
||||
|
||||
/// 更新配置
|
||||
pub async fn update_config(&self, new_config: ComfyUIConfig) -> Result<()> {
|
||||
// 验证新配置
|
||||
let validation = new_config.validate();
|
||||
if !validation.valid {
|
||||
return Err(anyhow!("配置验证失败: {:?}", validation.errors));
|
||||
}
|
||||
|
||||
let mut config = self.config.write().await;
|
||||
let old_base_url = config.base_url.clone();
|
||||
*config = new_config;
|
||||
|
||||
// 如果 URL 发生变化,需要重新连接
|
||||
if old_base_url != config.base_url {
|
||||
drop(config); // 释放锁
|
||||
info!("配置已更新,URL 发生变化,需要重新连接");
|
||||
self.disconnect().await?;
|
||||
} else {
|
||||
info!("配置已更新");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 获取当前配置
|
||||
pub async fn get_config(&self) -> ComfyUIConfig {
|
||||
self.config.read().await.clone()
|
||||
}
|
||||
|
||||
/// 获取客户端引用(用于高级操作)
|
||||
pub async fn get_client(&self) -> Result<Arc<ComfyUIClient>> {
|
||||
let client_guard = self.client.read().await;
|
||||
match client_guard.as_ref() {
|
||||
Some(client) => {
|
||||
// 创建一个新的 Arc 来避免持有锁
|
||||
Ok(Arc::new(client.clone()))
|
||||
}
|
||||
None => Err(anyhow!("客户端未连接")),
|
||||
}
|
||||
}
|
||||
|
||||
/// 自动重连
|
||||
pub async fn auto_reconnect(&self) -> Result<()> {
|
||||
info!("尝试自动重连");
|
||||
|
||||
// 先断开现有连接
|
||||
self.disconnect().await?;
|
||||
|
||||
// 等待一段时间后重连
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
|
||||
// 重新连接
|
||||
self.connect().await
|
||||
}
|
||||
|
||||
/// 检查是否需要健康检查
|
||||
pub async fn should_health_check(&self, interval: Duration) -> bool {
|
||||
let last_check = self.last_health_check.read().await;
|
||||
match *last_check {
|
||||
Some(last) => last.elapsed() >= interval,
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取连接统计信息
|
||||
pub async fn get_connection_stats(&self) -> ConnectionStats {
|
||||
let status = self.status.read().await.clone();
|
||||
let last_check = *self.last_health_check.read().await;
|
||||
let config = self.config.read().await;
|
||||
|
||||
ConnectionStats {
|
||||
status,
|
||||
base_url: config.base_url.clone(),
|
||||
last_health_check: last_check,
|
||||
timeout_seconds: config.timeout_seconds,
|
||||
retry_attempts: config.retry_attempts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 连接统计信息
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConnectionStats {
|
||||
pub status: ConnectionStatus,
|
||||
pub base_url: String,
|
||||
pub last_health_check: Option<std::time::Instant>,
|
||||
pub timeout_seconds: u64,
|
||||
pub retry_attempts: u32,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ConnectionStatus {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ConnectionStatus::Disconnected => write!(f, "未连接"),
|
||||
ConnectionStatus::Connecting => write!(f, "连接中"),
|
||||
ConnectionStatus::Connected => write!(f, "已连接"),
|
||||
ConnectionStatus::Failed(msg) => write!(f, "连接失败: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
315
apps/desktop/src-tauri/src/business/services/config_manager.rs
Normal file
315
apps/desktop/src-tauri/src/business/services/config_manager.rs
Normal file
@@ -0,0 +1,315 @@
|
||||
//! 配置管理服务
|
||||
//! 统一管理应用配置,支持配置验证和环境适配
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{info, warn, error, debug};
|
||||
|
||||
use crate::config::{AppConfig, ComfyUISettings};
|
||||
use crate::data::models::comfyui::{ComfyUIConfig, ValidationResult};
|
||||
|
||||
/// 环境类型
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Environment {
|
||||
/// 开发环境
|
||||
Development,
|
||||
/// 测试环境
|
||||
Testing,
|
||||
/// 生产环境
|
||||
Production,
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
/// 从环境变量获取环境类型
|
||||
pub fn from_env() -> Self {
|
||||
match std::env::var("MIXVIDEO_ENV").as_deref() {
|
||||
Ok("production") | Ok("prod") => Environment::Production,
|
||||
Ok("testing") | Ok("test") => Environment::Testing,
|
||||
_ => Environment::Development,
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取环境名称
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Environment::Development => "development",
|
||||
Environment::Testing => "testing",
|
||||
Environment::Production => "production",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 配置管理器
|
||||
pub struct ConfigManager {
|
||||
/// 应用配置
|
||||
app_config: Arc<RwLock<AppConfig>>,
|
||||
/// 当前环境
|
||||
environment: Environment,
|
||||
/// 配置文件路径
|
||||
config_path: Option<String>,
|
||||
}
|
||||
|
||||
impl ConfigManager {
|
||||
/// 创建新的配置管理器
|
||||
pub fn new(app_config: AppConfig, config_path: Option<String>) -> Self {
|
||||
let environment = Environment::from_env();
|
||||
info!("配置管理器初始化,环境: {}", environment.name());
|
||||
|
||||
Self {
|
||||
app_config: Arc::new(RwLock::new(app_config)),
|
||||
environment,
|
||||
config_path,
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取当前环境
|
||||
pub fn get_environment(&self) -> &Environment {
|
||||
&self.environment
|
||||
}
|
||||
|
||||
/// 获取应用配置
|
||||
pub async fn get_app_config(&self) -> AppConfig {
|
||||
self.app_config.read().await.clone()
|
||||
}
|
||||
|
||||
/// 更新应用配置
|
||||
pub async fn update_app_config(&self, config: AppConfig) -> Result<()> {
|
||||
// 验证配置
|
||||
let validation = self.validate_app_config(&config).await?;
|
||||
if !validation.valid {
|
||||
return Err(anyhow!("配置验证失败: {:?}", validation.errors));
|
||||
}
|
||||
|
||||
// 更新配置
|
||||
*self.app_config.write().await = config;
|
||||
|
||||
// 保存到文件
|
||||
self.save_config().await?;
|
||||
|
||||
info!("应用配置已更新");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 获取 ComfyUI 配置
|
||||
pub async fn get_comfyui_config(&self) -> ComfyUIConfig {
|
||||
let app_config = self.app_config.read().await;
|
||||
let mut config = app_config.comfyui_settings.to_comfyui_config();
|
||||
|
||||
// 根据环境调整配置
|
||||
self.apply_environment_config(&mut config);
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
/// 更新 ComfyUI 配置
|
||||
pub async fn update_comfyui_config(&self, config: ComfyUIConfig) -> Result<()> {
|
||||
// 验证配置
|
||||
let validation = config.validate();
|
||||
if !validation.valid {
|
||||
return Err(anyhow!("ComfyUI 配置验证失败: {:?}", validation.errors));
|
||||
}
|
||||
|
||||
// 更新应用配置中的 ComfyUI 设置
|
||||
{
|
||||
let mut app_config = self.app_config.write().await;
|
||||
app_config.comfyui_settings.update_from_comfyui_config(&config);
|
||||
}
|
||||
|
||||
// 保存到文件
|
||||
self.save_config().await?;
|
||||
|
||||
info!("ComfyUI 配置已更新");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 获取 ComfyUI 设置
|
||||
pub async fn get_comfyui_settings(&self) -> ComfyUISettings {
|
||||
let app_config = self.app_config.read().await;
|
||||
app_config.comfyui_settings.clone()
|
||||
}
|
||||
|
||||
/// 更新 ComfyUI 设置
|
||||
pub async fn update_comfyui_settings(&self, settings: ComfyUISettings) -> Result<()> {
|
||||
// 验证设置
|
||||
let validation = settings.validate();
|
||||
if !validation.valid {
|
||||
return Err(anyhow!("ComfyUI 设置验证失败: {:?}", validation.errors));
|
||||
}
|
||||
|
||||
// 更新配置
|
||||
{
|
||||
let mut app_config = self.app_config.write().await;
|
||||
app_config.comfyui_settings = settings;
|
||||
}
|
||||
|
||||
// 保存到文件
|
||||
self.save_config().await?;
|
||||
|
||||
info!("ComfyUI 设置已更新");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 重置 ComfyUI 配置为默认值
|
||||
pub async fn reset_comfyui_config(&self) -> Result<()> {
|
||||
let default_settings = ComfyUISettings::default();
|
||||
self.update_comfyui_settings(default_settings).await
|
||||
}
|
||||
|
||||
/// 验证应用配置
|
||||
async fn validate_app_config(&self, config: &AppConfig) -> Result<ValidationResult> {
|
||||
use crate::data::models::comfyui::{ValidationResult, ValidationError};
|
||||
|
||||
let mut result = ValidationResult::success();
|
||||
|
||||
// 验证主题
|
||||
if !["light", "dark", "auto"].contains(&config.theme.as_str()) {
|
||||
result.add_error(ValidationError::new("theme", "无效的主题设置"));
|
||||
}
|
||||
|
||||
// 验证语言
|
||||
if !["zh-CN", "en-US"].contains(&config.language.as_str()) {
|
||||
result.add_error(ValidationError::new("language", "无效的语言设置"));
|
||||
}
|
||||
|
||||
// 验证窗口大小
|
||||
if config.window_size.width <= 0.0 || config.window_size.height <= 0.0 {
|
||||
result.add_error(ValidationError::new("window_size", "窗口大小必须大于 0"));
|
||||
}
|
||||
|
||||
// 验证 ComfyUI 设置
|
||||
let comfyui_validation = config.comfyui_settings.validate();
|
||||
for error in comfyui_validation.errors {
|
||||
result.add_error(error);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 根据环境调整配置
|
||||
fn apply_environment_config(&self, config: &mut ComfyUIConfig) {
|
||||
match self.environment {
|
||||
Environment::Development => {
|
||||
// 开发环境:启用详细日志,较短超时
|
||||
config.timeout_seconds = std::cmp::min(config.timeout_seconds, 120);
|
||||
config.retry_attempts = std::cmp::min(config.retry_attempts, 2);
|
||||
debug!("应用开发环境配置");
|
||||
}
|
||||
Environment::Testing => {
|
||||
// 测试环境:中等超时,适中重试
|
||||
config.timeout_seconds = std::cmp::min(config.timeout_seconds, 180);
|
||||
config.retry_attempts = std::cmp::min(config.retry_attempts, 3);
|
||||
debug!("应用测试环境配置");
|
||||
}
|
||||
Environment::Production => {
|
||||
// 生产环境:较长超时,更多重试
|
||||
config.timeout_seconds = std::cmp::max(config.timeout_seconds, 300);
|
||||
config.retry_attempts = std::cmp::max(config.retry_attempts, 3);
|
||||
debug!("应用生产环境配置");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 保存配置到文件
|
||||
async fn save_config(&self) -> Result<()> {
|
||||
if let Some(config_path) = &self.config_path {
|
||||
let app_config = self.app_config.read().await;
|
||||
let config_json = serde_json::to_string_pretty(&*app_config)?;
|
||||
|
||||
tokio::fs::write(config_path, config_json).await
|
||||
.map_err(|e| anyhow!("保存配置文件失败: {}", e))?;
|
||||
|
||||
debug!("配置已保存到: {}", config_path);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 从文件加载配置
|
||||
pub async fn load_config(config_path: &str) -> Result<AppConfig> {
|
||||
match tokio::fs::read_to_string(config_path).await {
|
||||
Ok(content) => {
|
||||
let config: AppConfig = serde_json::from_str(&content)
|
||||
.map_err(|e| anyhow!("解析配置文件失败: {}", e))?;
|
||||
|
||||
info!("从文件加载配置: {}", config_path);
|
||||
Ok(config)
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("配置文件不存在,使用默认配置: {}", config_path);
|
||||
Ok(AppConfig::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 导出配置
|
||||
pub async fn export_config(&self) -> Result<String> {
|
||||
let app_config = self.app_config.read().await;
|
||||
serde_json::to_string_pretty(&*app_config)
|
||||
.map_err(|e| anyhow!("导出配置失败: {}", e))
|
||||
}
|
||||
|
||||
/// 导入配置
|
||||
pub async fn import_config(&self, config_json: &str) -> Result<()> {
|
||||
let config: AppConfig = serde_json::from_str(config_json)
|
||||
.map_err(|e| anyhow!("解析配置失败: {}", e))?;
|
||||
|
||||
self.update_app_config(config).await
|
||||
}
|
||||
|
||||
/// 获取配置统计信息
|
||||
pub async fn get_config_stats(&self) -> ConfigStats {
|
||||
let app_config = self.app_config.read().await;
|
||||
|
||||
ConfigStats {
|
||||
environment: self.environment.clone(),
|
||||
config_path: self.config_path.clone(),
|
||||
comfyui_enabled: app_config.comfyui_settings.enabled,
|
||||
sdk_enabled: app_config.comfyui_settings.is_sdk_enabled(),
|
||||
recent_projects_count: app_config.recent_projects.len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查配置健康状态
|
||||
pub async fn health_check(&self) -> Result<ConfigHealthStatus> {
|
||||
let app_config = self.app_config.read().await;
|
||||
let validation = self.validate_app_config(&*app_config).await?;
|
||||
|
||||
let status = if validation.valid {
|
||||
ConfigHealthStatus::Healthy
|
||||
} else {
|
||||
ConfigHealthStatus::Unhealthy(validation.errors)
|
||||
};
|
||||
|
||||
Ok(status)
|
||||
}
|
||||
}
|
||||
|
||||
/// 配置统计信息
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConfigStats {
|
||||
pub environment: Environment,
|
||||
pub config_path: Option<String>,
|
||||
pub comfyui_enabled: bool,
|
||||
pub sdk_enabled: bool,
|
||||
pub recent_projects_count: usize,
|
||||
}
|
||||
|
||||
/// 配置健康状态
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ConfigHealthStatus {
|
||||
/// 健康
|
||||
Healthy,
|
||||
/// 不健康
|
||||
Unhealthy(Vec<crate::data::models::comfyui::ValidationError>),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ConfigHealthStatus {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ConfigHealthStatus::Healthy => write!(f, "健康"),
|
||||
ConfigHealthStatus::Unhealthy(errors) => {
|
||||
write!(f, "不健康 ({} 个错误)", errors.len())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
375
apps/desktop/src-tauri/src/business/services/execution_engine.rs
Normal file
375
apps/desktop/src-tauri/src/business/services/execution_engine.rs
Normal file
@@ -0,0 +1,375 @@
|
||||
//! 执行引擎
|
||||
//! 负责工作流和模板的执行管理
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{info, warn, error, debug};
|
||||
|
||||
use comfyui_sdk::templates::WorkflowInstance;
|
||||
use comfyui_sdk::types::{ParameterValues, PromptRequest, ExecutionOptions};
|
||||
|
||||
use crate::business::services::comfyui_manager::ComfyUIManager;
|
||||
use crate::business::services::template_engine::TemplateEngine;
|
||||
use crate::data::models::comfyui::{ExecutionModel, ExecutionStatus, WorkflowModel};
|
||||
use crate::data::repositories::comfyui_repository::ComfyUIRepository;
|
||||
|
||||
/// 执行结果
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExecutionResult {
|
||||
/// 执行记录 ID
|
||||
pub execution_id: String,
|
||||
/// ComfyUI 提示 ID
|
||||
pub prompt_id: String,
|
||||
/// 执行状态
|
||||
pub status: ExecutionStatus,
|
||||
/// 输出文件 URLs
|
||||
pub output_urls: Vec<String>,
|
||||
/// 节点输出详情
|
||||
pub node_outputs: Option<HashMap<String, serde_json::Value>>,
|
||||
/// 执行时间(毫秒)
|
||||
pub execution_time: Option<u64>,
|
||||
/// 错误信息
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
/// 执行配置
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExecutionConfig {
|
||||
/// 超时时间
|
||||
pub timeout: Duration,
|
||||
/// 优先级
|
||||
pub priority: Option<i32>,
|
||||
/// 是否等待完成
|
||||
pub wait_for_completion: bool,
|
||||
/// 进度回调间隔
|
||||
pub progress_interval: Duration,
|
||||
}
|
||||
|
||||
impl Default for ExecutionConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
timeout: Duration::from_secs(300), // 5分钟默认超时
|
||||
priority: None,
|
||||
wait_for_completion: true,
|
||||
progress_interval: Duration::from_secs(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 执行引擎
|
||||
/// 提供工作流和模板的执行管理功能
|
||||
pub struct ExecutionEngine {
|
||||
/// ComfyUI 管理器
|
||||
manager: Arc<ComfyUIManager>,
|
||||
/// 模板引擎
|
||||
template_engine: Arc<TemplateEngine>,
|
||||
/// 数据仓库
|
||||
repository: Arc<ComfyUIRepository>,
|
||||
/// 正在执行的任务
|
||||
running_executions: Arc<RwLock<HashMap<String, ExecutionModel>>>,
|
||||
}
|
||||
|
||||
impl ExecutionEngine {
|
||||
/// 创建新的执行引擎
|
||||
pub fn new(
|
||||
manager: Arc<ComfyUIManager>,
|
||||
template_engine: Arc<TemplateEngine>,
|
||||
repository: Arc<ComfyUIRepository>,
|
||||
) -> Self {
|
||||
Self {
|
||||
manager,
|
||||
template_engine,
|
||||
repository,
|
||||
running_executions: Arc::new(RwLock::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// 执行工作流
|
||||
pub async fn execute_workflow(
|
||||
&self,
|
||||
workflow: &WorkflowModel,
|
||||
parameters: Option<ParameterValues>,
|
||||
config: ExecutionConfig,
|
||||
) -> Result<ExecutionResult> {
|
||||
info!("开始执行工作流: {} ({})", workflow.name, workflow.id);
|
||||
|
||||
// 检查连接状态
|
||||
if !self.manager.is_connected().await {
|
||||
return Err(anyhow!("ComfyUI 服务未连接"));
|
||||
}
|
||||
|
||||
// 获取客户端
|
||||
let client = self.manager.get_client().await?;
|
||||
|
||||
// 创建提示请求
|
||||
let prompt_request = PromptRequest {
|
||||
prompt: workflow.workflow_data.clone().into_iter().collect(),
|
||||
client_id: None,
|
||||
extra_data: None,
|
||||
};
|
||||
|
||||
// 提交工作流
|
||||
let prompt_response = client.http().queue_prompt(&prompt_request).await
|
||||
.map_err(|e| anyhow!("提交工作流失败: {}", e))?;
|
||||
|
||||
let prompt_id = prompt_response.prompt_id.clone();
|
||||
|
||||
// 创建执行记录
|
||||
let mut execution = ExecutionModel::for_workflow(
|
||||
workflow.id.clone(),
|
||||
prompt_id.clone(),
|
||||
parameters,
|
||||
);
|
||||
execution.update_status(ExecutionStatus::Running);
|
||||
|
||||
// 保存执行记录
|
||||
self.repository.create_execution(&execution).await?;
|
||||
|
||||
// 添加到运行中的任务
|
||||
{
|
||||
let mut running = self.running_executions.write().await;
|
||||
running.insert(execution.id.clone(), execution.clone());
|
||||
}
|
||||
|
||||
debug!("工作流已提交,prompt_id: {}, execution_id: {}", prompt_id, execution.id);
|
||||
|
||||
// 如果需要等待完成
|
||||
if config.wait_for_completion {
|
||||
self.wait_for_completion(&execution.id, config).await
|
||||
} else {
|
||||
Ok(ExecutionResult {
|
||||
execution_id: execution.id,
|
||||
prompt_id,
|
||||
status: ExecutionStatus::Running,
|
||||
output_urls: Vec::new(),
|
||||
node_outputs: None,
|
||||
execution_time: None,
|
||||
error_message: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 执行模板
|
||||
pub async fn execute_template(
|
||||
&self,
|
||||
template_id: &str,
|
||||
parameters: ParameterValues,
|
||||
config: ExecutionConfig,
|
||||
) -> Result<ExecutionResult> {
|
||||
info!("开始执行模板: {}", template_id);
|
||||
|
||||
// 检查连接状态
|
||||
if !self.manager.is_connected().await {
|
||||
return Err(anyhow!("ComfyUI 服务未连接"));
|
||||
}
|
||||
|
||||
// 创建模板实例
|
||||
let instance = self.template_engine.create_instance(template_id, parameters.clone()).await?;
|
||||
|
||||
// 获取客户端
|
||||
let client = self.manager.get_client().await?;
|
||||
|
||||
// 创建提示请求
|
||||
let prompt_request = PromptRequest {
|
||||
prompt: instance.get_workflow().clone().into_iter().collect(),
|
||||
client_id: None,
|
||||
extra_data: None,
|
||||
};
|
||||
|
||||
// 提交工作流
|
||||
let prompt_response = client.http().queue_prompt(&prompt_request).await
|
||||
.map_err(|e| anyhow!("提交模板失败: {}", e))?;
|
||||
|
||||
let prompt_id = prompt_response.prompt_id.clone();
|
||||
|
||||
// 创建执行记录
|
||||
let mut execution = ExecutionModel::for_template(
|
||||
template_id.to_string(),
|
||||
prompt_id.clone(),
|
||||
parameters,
|
||||
);
|
||||
execution.update_status(ExecutionStatus::Running);
|
||||
|
||||
// 保存执行记录
|
||||
self.repository.create_execution(&execution).await?;
|
||||
|
||||
// 添加到运行中的任务
|
||||
{
|
||||
let mut running = self.running_executions.write().await;
|
||||
running.insert(execution.id.clone(), execution.clone());
|
||||
}
|
||||
|
||||
debug!("模板已提交,prompt_id: {}, execution_id: {}", prompt_id, execution.id);
|
||||
|
||||
// 如果需要等待完成
|
||||
if config.wait_for_completion {
|
||||
self.wait_for_completion(&execution.id, config).await
|
||||
} else {
|
||||
Ok(ExecutionResult {
|
||||
execution_id: execution.id,
|
||||
prompt_id,
|
||||
status: ExecutionStatus::Running,
|
||||
output_urls: Vec::new(),
|
||||
node_outputs: None,
|
||||
execution_time: None,
|
||||
error_message: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 取消执行
|
||||
pub async fn cancel_execution(&self, execution_id: &str) -> Result<()> {
|
||||
info!("取消执行: {}", execution_id);
|
||||
|
||||
// 从运行中的任务移除
|
||||
let execution = {
|
||||
let mut running = self.running_executions.write().await;
|
||||
running.remove(execution_id)
|
||||
};
|
||||
|
||||
if let Some(mut execution) = execution {
|
||||
// 更新状态为已取消
|
||||
execution.update_status(ExecutionStatus::Cancelled);
|
||||
|
||||
// 更新数据库
|
||||
self.repository.update_execution(&execution).await?;
|
||||
|
||||
// TODO: 调用 ComfyUI API 取消任务
|
||||
// 这需要 SDK 支持取消操作
|
||||
|
||||
info!("执行已取消: {}", execution_id);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("执行记录不存在或已完成: {}", execution_id))
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取执行状态
|
||||
pub async fn get_execution_status(&self, execution_id: &str) -> Result<ExecutionResult> {
|
||||
// 先检查运行中的任务
|
||||
{
|
||||
let running = self.running_executions.read().await;
|
||||
if let Some(execution) = running.get(execution_id) {
|
||||
return Ok(ExecutionResult {
|
||||
execution_id: execution.id.clone(),
|
||||
prompt_id: execution.prompt_id.clone(),
|
||||
status: execution.status.clone(),
|
||||
output_urls: execution.output_urls.clone(),
|
||||
node_outputs: execution.node_outputs.clone(),
|
||||
execution_time: execution.execution_time,
|
||||
error_message: execution.error_message.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 从数据库查询
|
||||
match self.repository.get_execution(execution_id).await? {
|
||||
Some(execution) => Ok(ExecutionResult {
|
||||
execution_id: execution.id,
|
||||
prompt_id: execution.prompt_id,
|
||||
status: execution.status,
|
||||
output_urls: execution.output_urls,
|
||||
node_outputs: execution.node_outputs,
|
||||
execution_time: execution.execution_time,
|
||||
error_message: execution.error_message,
|
||||
}),
|
||||
None => Err(anyhow!("执行记录不存在: {}", execution_id)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 等待执行完成
|
||||
async fn wait_for_completion(&self, execution_id: &str, config: ExecutionConfig) -> Result<ExecutionResult> {
|
||||
let start_time = std::time::Instant::now();
|
||||
|
||||
loop {
|
||||
// 检查超时
|
||||
if start_time.elapsed() >= config.timeout {
|
||||
// 超时,取消执行
|
||||
let _ = self.cancel_execution(execution_id).await;
|
||||
return Err(anyhow!("执行超时: {}", execution_id));
|
||||
}
|
||||
|
||||
// 获取当前状态
|
||||
let status = self.get_execution_status(execution_id).await?;
|
||||
|
||||
match status.status {
|
||||
ExecutionStatus::Completed => {
|
||||
info!("执行完成: {}", execution_id);
|
||||
return Ok(status);
|
||||
}
|
||||
ExecutionStatus::Failed => {
|
||||
error!("执行失败: {}", execution_id);
|
||||
return Ok(status);
|
||||
}
|
||||
ExecutionStatus::Cancelled => {
|
||||
warn!("执行已取消: {}", execution_id);
|
||||
return Ok(status);
|
||||
}
|
||||
ExecutionStatus::Running | ExecutionStatus::Pending => {
|
||||
// 继续等待
|
||||
tokio::time::sleep(config.progress_interval).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取执行历史
|
||||
pub async fn get_execution_history(&self, limit: Option<u32>) -> Result<Vec<ExecutionResult>> {
|
||||
let executions = self.repository.list_executions(limit, None).await?;
|
||||
|
||||
let results = executions
|
||||
.into_iter()
|
||||
.map(|execution| ExecutionResult {
|
||||
execution_id: execution.id,
|
||||
prompt_id: execution.prompt_id,
|
||||
status: execution.status,
|
||||
output_urls: execution.output_urls,
|
||||
node_outputs: execution.node_outputs,
|
||||
execution_time: execution.execution_time,
|
||||
error_message: execution.error_message,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// 清理已完成的执行记录
|
||||
pub async fn cleanup_completed_executions(&self) {
|
||||
let mut running = self.running_executions.write().await;
|
||||
let mut to_remove = Vec::new();
|
||||
|
||||
for (id, execution) in running.iter() {
|
||||
if execution.is_completed() {
|
||||
to_remove.push(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for id in to_remove {
|
||||
running.remove(&id);
|
||||
}
|
||||
|
||||
if !running.is_empty() {
|
||||
debug!("清理完成,剩余运行中的任务: {}", running.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取运行统计信息
|
||||
pub async fn get_execution_stats(&self) -> ExecutionStats {
|
||||
let running = self.running_executions.read().await;
|
||||
|
||||
ExecutionStats {
|
||||
running_count: running.len(),
|
||||
running_executions: running.keys().cloned().collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 执行统计信息
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExecutionStats {
|
||||
pub running_count: usize,
|
||||
pub running_executions: Vec<String>,
|
||||
}
|
||||
@@ -35,9 +35,17 @@ pub mod conversation_service;
|
||||
pub mod jianying_export;
|
||||
pub mod template_segment_weight_service;
|
||||
pub mod directory_settings_service;
|
||||
// 旧的 ComfyUI 服务(将被逐步替换)
|
||||
pub mod comfyui_service;
|
||||
pub mod comfyui_sdk_service;
|
||||
pub mod comfyui_integration_service;
|
||||
|
||||
// 新的基于 SDK 的 ComfyUI 服务
|
||||
pub mod comfyui_manager;
|
||||
pub mod template_engine;
|
||||
pub mod execution_engine;
|
||||
pub mod realtime_monitor;
|
||||
pub mod config_manager;
|
||||
pub mod outfit_photo_generation_service;
|
||||
pub mod workflow_management_service;
|
||||
pub mod error_handling_service;
|
||||
|
||||
453
apps/desktop/src-tauri/src/business/services/realtime_monitor.rs
Normal file
453
apps/desktop/src-tauri/src/business/services/realtime_monitor.rs
Normal file
@@ -0,0 +1,453 @@
|
||||
//! 实时监控服务
|
||||
//! 负责 WebSocket 连接和实时事件处理
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::{RwLock, mpsc, broadcast};
|
||||
use tracing::{info, warn, error, debug};
|
||||
|
||||
use comfyui_sdk::types::events::{ComfyUIEvent, ExecutionEvent, QueueEvent, ProgressEvent};
|
||||
|
||||
use crate::business::services::comfyui_manager::ComfyUIManager;
|
||||
use crate::data::models::comfyui::{ExecutionModel, ExecutionStatus};
|
||||
use crate::data::repositories::comfyui_repository::ComfyUIRepository;
|
||||
|
||||
/// 实时事件类型
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RealtimeEvent {
|
||||
/// 执行开始
|
||||
ExecutionStarted {
|
||||
prompt_id: String,
|
||||
execution_id: Option<String>,
|
||||
},
|
||||
/// 执行进度更新
|
||||
ExecutionProgress {
|
||||
prompt_id: String,
|
||||
execution_id: Option<String>,
|
||||
progress: f32,
|
||||
current_node: Option<String>,
|
||||
total_nodes: Option<u32>,
|
||||
},
|
||||
/// 执行完成
|
||||
ExecutionCompleted {
|
||||
prompt_id: String,
|
||||
execution_id: Option<String>,
|
||||
outputs: HashMap<String, serde_json::Value>,
|
||||
output_urls: Vec<String>,
|
||||
},
|
||||
/// 执行失败
|
||||
ExecutionFailed {
|
||||
prompt_id: String,
|
||||
execution_id: Option<String>,
|
||||
error: String,
|
||||
},
|
||||
/// 队列状态更新
|
||||
QueueUpdated {
|
||||
running_count: u32,
|
||||
pending_count: u32,
|
||||
},
|
||||
/// 连接状态变化
|
||||
ConnectionChanged {
|
||||
connected: bool,
|
||||
message: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// 实时监控服务
|
||||
pub struct RealtimeMonitor {
|
||||
/// ComfyUI 管理器
|
||||
manager: Arc<ComfyUIManager>,
|
||||
/// 数据仓库
|
||||
repository: Arc<ComfyUIRepository>,
|
||||
/// 事件广播器
|
||||
event_sender: broadcast::Sender<RealtimeEvent>,
|
||||
/// WebSocket 连接状态
|
||||
websocket_connected: Arc<RwLock<bool>>,
|
||||
/// 提示 ID 到执行 ID 的映射
|
||||
prompt_execution_map: Arc<RwLock<HashMap<String, String>>>,
|
||||
/// 监控任务句柄
|
||||
monitor_handle: Arc<RwLock<Option<tokio::task::JoinHandle<()>>>>,
|
||||
}
|
||||
|
||||
impl RealtimeMonitor {
|
||||
/// 创建新的实时监控服务
|
||||
pub fn new(
|
||||
manager: Arc<ComfyUIManager>,
|
||||
repository: Arc<ComfyUIRepository>,
|
||||
) -> Self {
|
||||
let (event_sender, _) = broadcast::channel(1000);
|
||||
|
||||
Self {
|
||||
manager,
|
||||
repository,
|
||||
event_sender,
|
||||
websocket_connected: Arc::new(RwLock::new(false)),
|
||||
prompt_execution_map: Arc::new(RwLock::new(HashMap::new())),
|
||||
monitor_handle: Arc::new(RwLock::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 启动实时监控
|
||||
pub async fn start(&self) -> Result<()> {
|
||||
info!("启动实时监控服务");
|
||||
|
||||
// 检查是否已经在运行
|
||||
{
|
||||
let handle = self.monitor_handle.read().await;
|
||||
if handle.is_some() {
|
||||
return Err(anyhow!("实时监控服务已在运行"));
|
||||
}
|
||||
}
|
||||
|
||||
// 启动监控任务
|
||||
let monitor_task = self.spawn_monitor_task().await?;
|
||||
|
||||
{
|
||||
let mut handle = self.monitor_handle.write().await;
|
||||
*handle = Some(monitor_task);
|
||||
}
|
||||
|
||||
info!("实时监控服务已启动");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 停止实时监控
|
||||
pub async fn stop(&self) -> Result<()> {
|
||||
info!("停止实时监控服务");
|
||||
|
||||
let handle = {
|
||||
let mut handle_guard = self.monitor_handle.write().await;
|
||||
handle_guard.take()
|
||||
};
|
||||
|
||||
if let Some(handle) = handle {
|
||||
handle.abort();
|
||||
info!("实时监控服务已停止");
|
||||
}
|
||||
|
||||
// 更新连接状态
|
||||
*self.websocket_connected.write().await = false;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 订阅实时事件
|
||||
pub fn subscribe(&self) -> broadcast::Receiver<RealtimeEvent> {
|
||||
self.event_sender.subscribe()
|
||||
}
|
||||
|
||||
/// 注册执行映射
|
||||
pub async fn register_execution(&self, prompt_id: String, execution_id: String) {
|
||||
let mut map = self.prompt_execution_map.write().await;
|
||||
map.insert(prompt_id, execution_id);
|
||||
}
|
||||
|
||||
/// 获取执行 ID
|
||||
async fn get_execution_id(&self, prompt_id: &str) -> Option<String> {
|
||||
let map = self.prompt_execution_map.read().await;
|
||||
map.get(prompt_id).cloned()
|
||||
}
|
||||
|
||||
/// 生成监控任务
|
||||
async fn spawn_monitor_task(&self) -> Result<tokio::task::JoinHandle<()>> {
|
||||
let manager = Arc::clone(&self.manager);
|
||||
let repository = Arc::clone(&self.repository);
|
||||
let event_sender = self.event_sender.clone();
|
||||
let websocket_connected = Arc::clone(&self.websocket_connected);
|
||||
let prompt_execution_map = Arc::clone(&self.prompt_execution_map);
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
let mut reconnect_interval = Duration::from_secs(5);
|
||||
let max_reconnect_interval = Duration::from_secs(60);
|
||||
|
||||
loop {
|
||||
// 检查 ComfyUI 连接状态
|
||||
if !manager.is_connected().await {
|
||||
warn!("ComfyUI 未连接,等待连接...");
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 尝试建立 WebSocket 连接
|
||||
match Self::connect_websocket(&manager, &event_sender, &websocket_connected, &prompt_execution_map, &repository).await {
|
||||
Ok(_) => {
|
||||
info!("WebSocket 连接已建立");
|
||||
reconnect_interval = Duration::from_secs(5); // 重置重连间隔
|
||||
}
|
||||
Err(e) => {
|
||||
error!("WebSocket 连接失败: {}", e);
|
||||
|
||||
// 发送连接状态事件
|
||||
let _ = event_sender.send(RealtimeEvent::ConnectionChanged {
|
||||
connected: false,
|
||||
message: format!("WebSocket 连接失败: {}", e),
|
||||
});
|
||||
|
||||
// 等待后重试
|
||||
tokio::time::sleep(reconnect_interval).await;
|
||||
|
||||
// 增加重连间隔(指数退避)
|
||||
reconnect_interval = std::cmp::min(
|
||||
reconnect_interval * 2,
|
||||
max_reconnect_interval,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
/// 连接 WebSocket
|
||||
async fn connect_websocket(
|
||||
manager: &Arc<ComfyUIManager>,
|
||||
event_sender: &broadcast::Sender<RealtimeEvent>,
|
||||
websocket_connected: &Arc<RwLock<bool>>,
|
||||
prompt_execution_map: &Arc<RwLock<HashMap<String, String>>>,
|
||||
repository: &Arc<ComfyUIRepository>,
|
||||
) -> Result<()> {
|
||||
// 获取客户端
|
||||
let client = manager.get_client().await?;
|
||||
|
||||
// 建立 WebSocket 连接
|
||||
let mut websocket = client.websocket().connect().await?;
|
||||
|
||||
// 更新连接状态
|
||||
*websocket_connected.write().await = true;
|
||||
|
||||
// 发送连接状态事件
|
||||
let _ = event_sender.send(RealtimeEvent::ConnectionChanged {
|
||||
connected: true,
|
||||
message: "WebSocket 连接已建立".to_string(),
|
||||
});
|
||||
|
||||
// 处理 WebSocket 消息
|
||||
while let Some(event) = websocket.next_event().await {
|
||||
match event {
|
||||
Ok(comfyui_event) => {
|
||||
if let Err(e) = Self::handle_comfyui_event(
|
||||
comfyui_event,
|
||||
event_sender,
|
||||
prompt_execution_map,
|
||||
repository,
|
||||
).await {
|
||||
error!("处理 ComfyUI 事件失败: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("WebSocket 错误: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 连接断开
|
||||
*websocket_connected.write().await = false;
|
||||
|
||||
let _ = event_sender.send(RealtimeEvent::ConnectionChanged {
|
||||
connected: false,
|
||||
message: "WebSocket 连接已断开".to_string(),
|
||||
});
|
||||
|
||||
Err(anyhow!("WebSocket 连接断开"))
|
||||
}
|
||||
|
||||
/// 处理 ComfyUI 事件
|
||||
async fn handle_comfyui_event(
|
||||
event: ComfyUIEvent,
|
||||
event_sender: &broadcast::Sender<RealtimeEvent>,
|
||||
prompt_execution_map: &Arc<RwLock<HashMap<String, String>>>,
|
||||
repository: &Arc<ComfyUIRepository>,
|
||||
) -> Result<()> {
|
||||
match event {
|
||||
ComfyUIEvent::Execution(exec_event) => {
|
||||
Self::handle_execution_event(exec_event, event_sender, prompt_execution_map, repository).await?;
|
||||
}
|
||||
ComfyUIEvent::Progress(progress_event) => {
|
||||
Self::handle_progress_event(progress_event, event_sender, prompt_execution_map).await?;
|
||||
}
|
||||
ComfyUIEvent::Queue(queue_event) => {
|
||||
Self::handle_queue_event(queue_event, event_sender).await?;
|
||||
}
|
||||
_ => {
|
||||
debug!("收到其他类型事件: {:?}", event);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 处理执行事件
|
||||
async fn handle_execution_event(
|
||||
event: ExecutionEvent,
|
||||
event_sender: &broadcast::Sender<RealtimeEvent>,
|
||||
prompt_execution_map: &Arc<RwLock<HashMap<String, String>>>,
|
||||
repository: &Arc<ComfyUIRepository>,
|
||||
) -> Result<()> {
|
||||
let execution_id = {
|
||||
let map = prompt_execution_map.read().await;
|
||||
map.get(&event.prompt_id).cloned()
|
||||
};
|
||||
|
||||
match event.event_type.as_str() {
|
||||
"execution_start" => {
|
||||
let _ = event_sender.send(RealtimeEvent::ExecutionStarted {
|
||||
prompt_id: event.prompt_id.clone(),
|
||||
execution_id: execution_id.clone(),
|
||||
});
|
||||
|
||||
// 更新数据库中的执行状态
|
||||
if let Some(exec_id) = execution_id {
|
||||
if let Ok(Some(mut execution)) = repository.get_execution(&exec_id).await {
|
||||
execution.update_status(ExecutionStatus::Running);
|
||||
let _ = repository.update_execution(&execution).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
"execution_complete" => {
|
||||
// 提取输出信息
|
||||
let outputs = event.data.unwrap_or_default();
|
||||
let output_urls = Self::extract_output_urls(&outputs);
|
||||
|
||||
let _ = event_sender.send(RealtimeEvent::ExecutionCompleted {
|
||||
prompt_id: event.prompt_id.clone(),
|
||||
execution_id: execution_id.clone(),
|
||||
outputs: outputs.clone(),
|
||||
output_urls: output_urls.clone(),
|
||||
});
|
||||
|
||||
// 更新数据库中的执行状态
|
||||
if let Some(exec_id) = execution_id {
|
||||
if let Ok(Some(mut execution)) = repository.get_execution(&exec_id).await {
|
||||
execution.set_results(outputs, output_urls);
|
||||
execution.node_outputs = Some(outputs);
|
||||
let _ = repository.update_execution(&execution).await;
|
||||
}
|
||||
}
|
||||
|
||||
// 清理映射
|
||||
{
|
||||
let mut map = prompt_execution_map.write().await;
|
||||
map.remove(&event.prompt_id);
|
||||
}
|
||||
}
|
||||
"execution_error" => {
|
||||
let error_msg = event.data
|
||||
.and_then(|data| data.get("error"))
|
||||
.and_then(|e| e.as_str())
|
||||
.unwrap_or("未知错误")
|
||||
.to_string();
|
||||
|
||||
let _ = event_sender.send(RealtimeEvent::ExecutionFailed {
|
||||
prompt_id: event.prompt_id.clone(),
|
||||
execution_id: execution_id.clone(),
|
||||
error: error_msg.clone(),
|
||||
});
|
||||
|
||||
// 更新数据库中的执行状态
|
||||
if let Some(exec_id) = execution_id {
|
||||
if let Ok(Some(mut execution)) = repository.get_execution(&exec_id).await {
|
||||
execution.set_error(error_msg);
|
||||
let _ = repository.update_execution(&execution).await;
|
||||
}
|
||||
}
|
||||
|
||||
// 清理映射
|
||||
{
|
||||
let mut map = prompt_execution_map.write().await;
|
||||
map.remove(&event.prompt_id);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
debug!("未处理的执行事件类型: {}", event.event_type);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 处理进度事件
|
||||
async fn handle_progress_event(
|
||||
event: ProgressEvent,
|
||||
event_sender: &broadcast::Sender<RealtimeEvent>,
|
||||
prompt_execution_map: &Arc<RwLock<HashMap<String, String>>>,
|
||||
) -> Result<()> {
|
||||
let execution_id = {
|
||||
let map = prompt_execution_map.read().await;
|
||||
map.get(&event.prompt_id).cloned()
|
||||
};
|
||||
|
||||
let _ = event_sender.send(RealtimeEvent::ExecutionProgress {
|
||||
prompt_id: event.prompt_id,
|
||||
execution_id,
|
||||
progress: event.progress,
|
||||
current_node: event.current_node,
|
||||
total_nodes: event.total_nodes,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 处理队列事件
|
||||
async fn handle_queue_event(
|
||||
event: QueueEvent,
|
||||
event_sender: &broadcast::Sender<RealtimeEvent>,
|
||||
) -> Result<()> {
|
||||
let _ = event_sender.send(RealtimeEvent::QueueUpdated {
|
||||
running_count: event.running_count,
|
||||
pending_count: event.pending_count,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 提取输出 URLs
|
||||
fn extract_output_urls(outputs: &HashMap<String, serde_json::Value>) -> Vec<String> {
|
||||
let mut urls = Vec::new();
|
||||
|
||||
for (_, output) in outputs {
|
||||
if let Some(images) = output.get("images").and_then(|v| v.as_array()) {
|
||||
for image in images {
|
||||
if let Some(filename) = image.get("filename").and_then(|v| v.as_str()) {
|
||||
// 构建完整的 URL(这里需要根据实际的 ComfyUI 配置调整)
|
||||
let url = format!("/view?filename={}", filename);
|
||||
urls.push(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
urls
|
||||
}
|
||||
|
||||
/// 检查 WebSocket 连接状态
|
||||
pub async fn is_websocket_connected(&self) -> bool {
|
||||
*self.websocket_connected.read().await
|
||||
}
|
||||
|
||||
/// 获取监控统计信息
|
||||
pub async fn get_monitor_stats(&self) -> MonitorStats {
|
||||
let websocket_connected = *self.websocket_connected.read().await;
|
||||
let prompt_map_size = self.prompt_execution_map.read().await.len();
|
||||
let is_running = self.monitor_handle.read().await.is_some();
|
||||
|
||||
MonitorStats {
|
||||
is_running,
|
||||
websocket_connected,
|
||||
tracked_executions: prompt_map_size,
|
||||
event_subscribers: self.event_sender.receiver_count(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 监控统计信息
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MonitorStats {
|
||||
pub is_running: bool,
|
||||
pub websocket_connected: bool,
|
||||
pub tracked_executions: usize,
|
||||
pub event_subscribers: usize,
|
||||
}
|
||||
301
apps/desktop/src-tauri/src/business/services/template_engine.rs
Normal file
301
apps/desktop/src-tauri/src/business/services/template_engine.rs
Normal file
@@ -0,0 +1,301 @@
|
||||
//! 模板引擎
|
||||
//! 负责工作流模板的管理、验证和实例化
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tracing::{info, warn, error, debug};
|
||||
|
||||
use comfyui_sdk::templates::{WorkflowTemplate, WorkflowInstance, TemplateManager};
|
||||
use comfyui_sdk::types::{ParameterValues, ValidationResult, ValidationError, ComfyUIWorkflow};
|
||||
|
||||
use crate::data::models::comfyui::{TemplateModel, WorkflowModel};
|
||||
use crate::data::repositories::comfyui_repository::ComfyUIRepository;
|
||||
|
||||
/// 模板引擎
|
||||
/// 提供模板管理和实例化功能
|
||||
pub struct TemplateEngine {
|
||||
/// 数据仓库
|
||||
repository: Arc<ComfyUIRepository>,
|
||||
/// SDK 模板管理器
|
||||
template_manager: TemplateManager,
|
||||
/// 内存中的模板缓存
|
||||
template_cache: Arc<tokio::sync::RwLock<HashMap<String, TemplateModel>>>,
|
||||
}
|
||||
|
||||
impl TemplateEngine {
|
||||
/// 创建新的模板引擎
|
||||
pub fn new(repository: Arc<ComfyUIRepository>) -> Self {
|
||||
Self {
|
||||
repository,
|
||||
template_manager: TemplateManager::new(),
|
||||
template_cache: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// 加载模板
|
||||
pub async fn load_template(&self, template_id: &str) -> Result<TemplateModel> {
|
||||
// 先检查缓存
|
||||
{
|
||||
let cache = self.template_cache.read().await;
|
||||
if let Some(template) = cache.get(template_id) {
|
||||
debug!("从缓存加载模板: {}", template_id);
|
||||
return Ok(template.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// 从数据库加载
|
||||
match self.repository.get_template(template_id).await? {
|
||||
Some(template) => {
|
||||
// 更新缓存
|
||||
{
|
||||
let mut cache = self.template_cache.write().await;
|
||||
cache.insert(template_id.to_string(), template.clone());
|
||||
}
|
||||
|
||||
debug!("从数据库加载模板: {}", template_id);
|
||||
Ok(template)
|
||||
}
|
||||
None => Err(anyhow!("模板不存在: {}", template_id)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取所有模板
|
||||
pub async fn list_templates(&self, enabled_only: bool) -> Result<Vec<TemplateModel>> {
|
||||
self.repository.list_templates(enabled_only).await
|
||||
}
|
||||
|
||||
/// 按分类获取模板
|
||||
pub async fn get_templates_by_category(&self, category: &str) -> Result<Vec<TemplateModel>> {
|
||||
self.repository.get_templates_by_category(category).await
|
||||
}
|
||||
|
||||
/// 创建模板
|
||||
pub async fn create_template(&self, template: TemplateModel) -> Result<()> {
|
||||
// 验证模板
|
||||
self.validate_template_structure(&template)?;
|
||||
|
||||
// 保存到数据库
|
||||
self.repository.create_template(&template).await?;
|
||||
|
||||
// 更新缓存
|
||||
{
|
||||
let mut cache = self.template_cache.write().await;
|
||||
cache.insert(template.id.clone(), template.clone());
|
||||
}
|
||||
|
||||
info!("创建模板成功: {} ({})", template.name, template.id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 更新模板
|
||||
pub async fn update_template(&self, template: TemplateModel) -> Result<()> {
|
||||
// 验证模板
|
||||
self.validate_template_structure(&template)?;
|
||||
|
||||
// 更新数据库
|
||||
self.repository.update_template(&template).await?;
|
||||
|
||||
// 更新缓存
|
||||
{
|
||||
let mut cache = self.template_cache.write().await;
|
||||
cache.insert(template.id.clone(), template.clone());
|
||||
}
|
||||
|
||||
info!("更新模板成功: {} ({})", template.name, template.id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 删除模板
|
||||
pub async fn delete_template(&self, template_id: &str) -> Result<()> {
|
||||
// 从数据库删除
|
||||
self.repository.delete_template(template_id).await?;
|
||||
|
||||
// 从缓存删除
|
||||
{
|
||||
let mut cache = self.template_cache.write().await;
|
||||
cache.remove(template_id);
|
||||
}
|
||||
|
||||
info!("删除模板成功: {}", template_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 验证模板参数
|
||||
pub async fn validate_parameters(&self, template_id: &str, parameters: &ParameterValues) -> Result<ValidationResult> {
|
||||
let template = self.load_template(template_id).await?;
|
||||
Ok(template.validate_parameters(parameters))
|
||||
}
|
||||
|
||||
/// 创建模板实例
|
||||
pub async fn create_instance(&self, template_id: &str, parameters: ParameterValues) -> Result<WorkflowInstance> {
|
||||
let template = self.load_template(template_id).await?;
|
||||
|
||||
// 验证参数
|
||||
let validation = template.validate_parameters(¶meters);
|
||||
if !validation.valid {
|
||||
return Err(anyhow!("参数验证失败: {:?}", validation.errors));
|
||||
}
|
||||
|
||||
// 使用 SDK 创建工作流模板
|
||||
let workflow_template = WorkflowTemplate::new(
|
||||
template.template_data.clone(),
|
||||
template.parameter_schema.clone(),
|
||||
)?;
|
||||
|
||||
// 创建实例
|
||||
let instance = workflow_template.create_instance(parameters)?;
|
||||
|
||||
debug!("创建模板实例成功: {} -> {}", template_id, instance.get_id());
|
||||
Ok(instance)
|
||||
}
|
||||
|
||||
/// 从工作流创建模板
|
||||
pub async fn create_template_from_workflow(&self, workflow: &WorkflowModel, parameter_schema: HashMap<String, comfyui_sdk::types::ParameterSchema>) -> Result<TemplateModel> {
|
||||
// 创建模板元数据
|
||||
let template_metadata = comfyui_sdk::types::TemplateMetadata {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
name: format!("{} Template", workflow.name),
|
||||
description: workflow.description.clone(),
|
||||
version: Some(workflow.version.clone()),
|
||||
author: None,
|
||||
tags: Some(workflow.tags.clone()),
|
||||
category: workflow.category.clone(),
|
||||
created_at: Some(chrono::Utc::now()),
|
||||
updated_at: Some(chrono::Utc::now()),
|
||||
};
|
||||
|
||||
// 创建模板数据
|
||||
let template_data = comfyui_sdk::types::WorkflowTemplateData {
|
||||
metadata: template_metadata.clone(),
|
||||
workflow: workflow.workflow_data.clone(),
|
||||
parameters: parameter_schema.clone(),
|
||||
};
|
||||
|
||||
// 创建模板模型
|
||||
let template = TemplateModel::new(
|
||||
template_metadata.name.clone(),
|
||||
template_data,
|
||||
parameter_schema,
|
||||
);
|
||||
|
||||
Ok(template)
|
||||
}
|
||||
|
||||
/// 预览模板实例(不执行)
|
||||
pub async fn preview_instance(&self, template_id: &str, parameters: ParameterValues) -> Result<ComfyUIWorkflow> {
|
||||
let instance = self.create_instance(template_id, parameters).await?;
|
||||
Ok(instance.get_workflow().clone())
|
||||
}
|
||||
|
||||
/// 获取模板的参数定义
|
||||
pub async fn get_parameter_schema(&self, template_id: &str) -> Result<HashMap<String, comfyui_sdk::types::ParameterSchema>> {
|
||||
let template = self.load_template(template_id).await?;
|
||||
Ok(template.parameter_schema)
|
||||
}
|
||||
|
||||
/// 验证模板结构
|
||||
fn validate_template_structure(&self, template: &TemplateModel) -> Result<()> {
|
||||
// 检查模板名称
|
||||
if template.name.trim().is_empty() {
|
||||
return Err(anyhow!("模板名称不能为空"));
|
||||
}
|
||||
|
||||
// 检查工作流数据
|
||||
if template.template_data.workflow.is_empty() {
|
||||
return Err(anyhow!("工作流数据不能为空"));
|
||||
}
|
||||
|
||||
// 检查参数定义
|
||||
if template.parameter_schema.is_empty() {
|
||||
warn!("模板 {} 没有定义参数", template.id);
|
||||
}
|
||||
|
||||
// 验证工作流结构
|
||||
for (node_id, node) in &template.template_data.workflow {
|
||||
if node.class_type.trim().is_empty() {
|
||||
return Err(anyhow!("节点 {} 的 class_type 不能为空", node_id));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 清除模板缓存
|
||||
pub async fn clear_cache(&self) {
|
||||
let mut cache = self.template_cache.write().await;
|
||||
cache.clear();
|
||||
info!("模板缓存已清除");
|
||||
}
|
||||
|
||||
/// 预热模板缓存
|
||||
pub async fn warm_cache(&self) -> Result<()> {
|
||||
let templates = self.repository.list_templates(true).await?;
|
||||
|
||||
{
|
||||
let mut cache = self.template_cache.write().await;
|
||||
for template in templates {
|
||||
cache.insert(template.id.clone(), template);
|
||||
}
|
||||
}
|
||||
|
||||
info!("模板缓存预热完成");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 获取缓存统计信息
|
||||
pub async fn get_cache_stats(&self) -> CacheStats {
|
||||
let cache = self.template_cache.read().await;
|
||||
CacheStats {
|
||||
cached_templates: cache.len(),
|
||||
cache_keys: cache.keys().cloned().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 搜索模板
|
||||
pub async fn search_templates(&self, query: &str) -> Result<Vec<TemplateModel>> {
|
||||
let all_templates = self.repository.list_templates(true).await?;
|
||||
|
||||
let query_lower = query.to_lowercase();
|
||||
let filtered_templates: Vec<TemplateModel> = all_templates
|
||||
.into_iter()
|
||||
.filter(|template| {
|
||||
template.name.to_lowercase().contains(&query_lower) ||
|
||||
template.description.as_ref().map_or(false, |desc| desc.to_lowercase().contains(&query_lower)) ||
|
||||
template.tags.iter().any(|tag| tag.to_lowercase().contains(&query_lower))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(filtered_templates)
|
||||
}
|
||||
|
||||
/// 导出模板
|
||||
pub async fn export_template(&self, template_id: &str) -> Result<String> {
|
||||
let template = self.load_template(template_id).await?;
|
||||
serde_json::to_string_pretty(&template)
|
||||
.map_err(|e| anyhow!("导出模板失败: {}", e))
|
||||
}
|
||||
|
||||
/// 导入模板
|
||||
pub async fn import_template(&self, template_json: &str) -> Result<String> {
|
||||
let mut template: TemplateModel = serde_json::from_str(template_json)
|
||||
.map_err(|e| anyhow!("解析模板失败: {}", e))?;
|
||||
|
||||
// 生成新的 ID 避免冲突
|
||||
template.id = uuid::Uuid::new_v4().to_string();
|
||||
template.created_at = chrono::Utc::now();
|
||||
template.updated_at = chrono::Utc::now();
|
||||
|
||||
// 创建模板
|
||||
self.create_template(template.clone()).await?;
|
||||
|
||||
Ok(template.id)
|
||||
}
|
||||
}
|
||||
|
||||
/// 缓存统计信息
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CacheStats {
|
||||
pub cached_templates: usize,
|
||||
pub cache_keys: Vec<String>,
|
||||
}
|
||||
@@ -135,6 +135,71 @@ impl ComfyUISettings {
|
||||
pub fn get_sdk_timeout(&self) -> std::time::Duration {
|
||||
std::time::Duration::from_secs(self.sdk_config.sdk_timeout_seconds)
|
||||
}
|
||||
|
||||
/// 转换为新的 ComfyUI 配置
|
||||
pub fn to_comfyui_config(&self) -> crate::data::models::comfyui::ComfyUIConfig {
|
||||
use crate::data::models::comfyui::ComfyUIConfig;
|
||||
|
||||
ComfyUIConfig {
|
||||
base_url: self.base_url(),
|
||||
timeout_seconds: self.timeout_seconds,
|
||||
retry_attempts: self.sdk_config.sdk_retry_attempts,
|
||||
retry_delay_ms: 1000, // 默认 1 秒重试延迟
|
||||
enable_websocket: true, // 默认启用 WebSocket
|
||||
enable_cache: true, // 默认启用缓存
|
||||
max_concurrency: 4, // 默认并发数
|
||||
custom_headers: None, // 暂时不支持自定义头
|
||||
}
|
||||
}
|
||||
|
||||
/// 从新的 ComfyUI 配置更新设置
|
||||
pub fn update_from_comfyui_config(&mut self, config: &crate::data::models::comfyui::ComfyUIConfig) {
|
||||
// 解析 URL
|
||||
if let Ok(url) = url::Url::parse(&config.base_url) {
|
||||
if let Some(host) = url.host_str() {
|
||||
self.server_address = host.to_string();
|
||||
}
|
||||
if let Some(port) = url.port() {
|
||||
self.server_port = port;
|
||||
}
|
||||
}
|
||||
|
||||
self.timeout_seconds = config.timeout_seconds;
|
||||
self.sdk_config.sdk_retry_attempts = config.retry_attempts;
|
||||
}
|
||||
|
||||
/// 验证配置
|
||||
pub fn validate(&self) -> crate::data::models::comfyui::ValidationResult {
|
||||
use crate::data::models::comfyui::{ValidationResult, ValidationError};
|
||||
|
||||
let mut result = ValidationResult::success();
|
||||
|
||||
// 验证服务器地址
|
||||
if self.server_address.trim().is_empty() {
|
||||
result.add_error(ValidationError::new("server_address", "服务器地址不能为空"));
|
||||
}
|
||||
|
||||
// 验证端口
|
||||
if self.server_port == 0 {
|
||||
result.add_error(ValidationError::new("server_port", "端口号必须大于 0"));
|
||||
}
|
||||
|
||||
// 验证超时时间
|
||||
if self.timeout_seconds == 0 {
|
||||
result.add_error(ValidationError::new("timeout_seconds", "超时时间必须大于 0"));
|
||||
}
|
||||
|
||||
// 验证 SDK 配置
|
||||
if self.sdk_config.sdk_retry_attempts > 10 {
|
||||
result.add_error(ValidationError::new("sdk_retry_attempts", "重试次数不应超过 10"));
|
||||
}
|
||||
|
||||
if self.sdk_config.sdk_timeout_seconds == 0 {
|
||||
result.add_error(ValidationError::new("sdk_timeout_seconds", "SDK 超时时间必须大于 0"));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AppConfig {
|
||||
|
||||
Reference in New Issue
Block a user