From b7edee768836e3ef461136c64b45eec163eb4b91 Mon Sep 17 00:00:00 2001 From: imeepos Date: Fri, 8 Aug 2025 14:38:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9F=BA=E7=A1=80=E6=9E=B6=E6=9E=84?= =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/business/services/comfyui_manager.rs | 278 +++++++++++ .../src/business/services/config_manager.rs | 315 ++++++++++++ .../src/business/services/execution_engine.rs | 375 +++++++++++++++ .../src-tauri/src/business/services/mod.rs | 8 + .../src/business/services/realtime_monitor.rs | 453 ++++++++++++++++++ .../src/business/services/template_engine.rs | 301 ++++++++++++ apps/desktop/src-tauri/src/config.rs | 65 +++ 7 files changed, 1795 insertions(+) create mode 100644 apps/desktop/src-tauri/src/business/services/comfyui_manager.rs create mode 100644 apps/desktop/src-tauri/src/business/services/config_manager.rs create mode 100644 apps/desktop/src-tauri/src/business/services/execution_engine.rs create mode 100644 apps/desktop/src-tauri/src/business/services/realtime_monitor.rs create mode 100644 apps/desktop/src-tauri/src/business/services/template_engine.rs diff --git a/apps/desktop/src-tauri/src/business/services/comfyui_manager.rs b/apps/desktop/src-tauri/src/business/services/comfyui_manager.rs new file mode 100644 index 0000000..028e737 --- /dev/null +++ b/apps/desktop/src-tauri/src/business/services/comfyui_manager.rs @@ -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>>, + /// 配置信息 + config: Arc>, + /// 连接状态 + status: Arc>, + /// 最后健康检查时间 + last_health_check: Arc>>, +} + +impl ComfyUIManager { + /// 创建新的 ComfyUI 管理器 + pub fn new(config: ComfyUIConfig) -> Result { + // 验证配置 + 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 { + 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 { + 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 { + 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 { + 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> { + 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, + 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), + } + } +} diff --git a/apps/desktop/src-tauri/src/business/services/config_manager.rs b/apps/desktop/src-tauri/src/business/services/config_manager.rs new file mode 100644 index 0000000..c4b9b0c --- /dev/null +++ b/apps/desktop/src-tauri/src/business/services/config_manager.rs @@ -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>, + /// 当前环境 + environment: Environment, + /// 配置文件路径 + config_path: Option, +} + +impl ConfigManager { + /// 创建新的配置管理器 + pub fn new(app_config: AppConfig, config_path: Option) -> 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 { + 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 { + 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 { + 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 { + 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, + pub comfyui_enabled: bool, + pub sdk_enabled: bool, + pub recent_projects_count: usize, +} + +/// 配置健康状态 +#[derive(Debug, Clone)] +pub enum ConfigHealthStatus { + /// 健康 + Healthy, + /// 不健康 + Unhealthy(Vec), +} + +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()) + } + } + } +} diff --git a/apps/desktop/src-tauri/src/business/services/execution_engine.rs b/apps/desktop/src-tauri/src/business/services/execution_engine.rs new file mode 100644 index 0000000..2a58a7e --- /dev/null +++ b/apps/desktop/src-tauri/src/business/services/execution_engine.rs @@ -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, + /// 节点输出详情 + pub node_outputs: Option>, + /// 执行时间(毫秒) + pub execution_time: Option, + /// 错误信息 + pub error_message: Option, +} + +/// 执行配置 +#[derive(Debug, Clone)] +pub struct ExecutionConfig { + /// 超时时间 + pub timeout: Duration, + /// 优先级 + pub priority: Option, + /// 是否等待完成 + 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, + /// 模板引擎 + template_engine: Arc, + /// 数据仓库 + repository: Arc, + /// 正在执行的任务 + running_executions: Arc>>, +} + +impl ExecutionEngine { + /// 创建新的执行引擎 + pub fn new( + manager: Arc, + template_engine: Arc, + repository: Arc, + ) -> Self { + Self { + manager, + template_engine, + repository, + running_executions: Arc::new(RwLock::new(HashMap::new())), + } + } + + /// 执行工作流 + pub async fn execute_workflow( + &self, + workflow: &WorkflowModel, + parameters: Option, + config: ExecutionConfig, + ) -> Result { + 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 { + 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 { + // 先检查运行中的任务 + { + 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 { + 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) -> Result> { + 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, +} diff --git a/apps/desktop/src-tauri/src/business/services/mod.rs b/apps/desktop/src-tauri/src/business/services/mod.rs index c3b6b42..322491d 100644 --- a/apps/desktop/src-tauri/src/business/services/mod.rs +++ b/apps/desktop/src-tauri/src/business/services/mod.rs @@ -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; diff --git a/apps/desktop/src-tauri/src/business/services/realtime_monitor.rs b/apps/desktop/src-tauri/src/business/services/realtime_monitor.rs new file mode 100644 index 0000000..ba257e8 --- /dev/null +++ b/apps/desktop/src-tauri/src/business/services/realtime_monitor.rs @@ -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, + }, + /// 执行进度更新 + ExecutionProgress { + prompt_id: String, + execution_id: Option, + progress: f32, + current_node: Option, + total_nodes: Option, + }, + /// 执行完成 + ExecutionCompleted { + prompt_id: String, + execution_id: Option, + outputs: HashMap, + output_urls: Vec, + }, + /// 执行失败 + ExecutionFailed { + prompt_id: String, + execution_id: Option, + error: String, + }, + /// 队列状态更新 + QueueUpdated { + running_count: u32, + pending_count: u32, + }, + /// 连接状态变化 + ConnectionChanged { + connected: bool, + message: String, + }, +} + +/// 实时监控服务 +pub struct RealtimeMonitor { + /// ComfyUI 管理器 + manager: Arc, + /// 数据仓库 + repository: Arc, + /// 事件广播器 + event_sender: broadcast::Sender, + /// WebSocket 连接状态 + websocket_connected: Arc>, + /// 提示 ID 到执行 ID 的映射 + prompt_execution_map: Arc>>, + /// 监控任务句柄 + monitor_handle: Arc>>>, +} + +impl RealtimeMonitor { + /// 创建新的实时监控服务 + pub fn new( + manager: Arc, + repository: Arc, + ) -> 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 { + 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 { + let map = self.prompt_execution_map.read().await; + map.get(prompt_id).cloned() + } + + /// 生成监控任务 + async fn spawn_monitor_task(&self) -> Result> { + 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, + event_sender: &broadcast::Sender, + websocket_connected: &Arc>, + prompt_execution_map: &Arc>>, + repository: &Arc, + ) -> 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, + prompt_execution_map: &Arc>>, + repository: &Arc, + ) -> 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, + prompt_execution_map: &Arc>>, + repository: &Arc, + ) -> 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, + prompt_execution_map: &Arc>>, + ) -> 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, + ) -> 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) -> Vec { + 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, +} diff --git a/apps/desktop/src-tauri/src/business/services/template_engine.rs b/apps/desktop/src-tauri/src/business/services/template_engine.rs new file mode 100644 index 0000000..e6c060e --- /dev/null +++ b/apps/desktop/src-tauri/src/business/services/template_engine.rs @@ -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, + /// SDK 模板管理器 + template_manager: TemplateManager, + /// 内存中的模板缓存 + template_cache: Arc>>, +} + +impl TemplateEngine { + /// 创建新的模板引擎 + pub fn new(repository: Arc) -> 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 { + // 先检查缓存 + { + 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> { + self.repository.list_templates(enabled_only).await + } + + /// 按分类获取模板 + pub async fn get_templates_by_category(&self, category: &str) -> Result> { + 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 { + 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 { + 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) -> Result { + // 创建模板元数据 + 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 { + 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> { + 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> { + let all_templates = self.repository.list_templates(true).await?; + + let query_lower = query.to_lowercase(); + let filtered_templates: Vec = 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 { + 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 { + 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, +} diff --git a/apps/desktop/src-tauri/src/config.rs b/apps/desktop/src-tauri/src/config.rs index 65c4402..815dd79 100644 --- a/apps/desktop/src-tauri/src/config.rs +++ b/apps/desktop/src-tauri/src/config.rs @@ -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 {