diff --git a/apps/desktop/src-tauri/src/business/services/service_manager.rs b/apps/desktop/src-tauri/src/business/services/service_manager.rs index 558abb0..ba8a7bb 100644 --- a/apps/desktop/src-tauri/src/business/services/service_manager.rs +++ b/apps/desktop/src-tauri/src/business/services/service_manager.rs @@ -159,6 +159,27 @@ impl ServiceManager { } } + /// 获取当前配置 + pub async fn get_config(&self) -> Result { + // 从数据库加载配置 + self.repository.get_config().await + } + + /// 更新配置 + pub async fn update_config(&self, config: crate::data::models::comfyui::ComfyUIConfig) -> Result<()> { + // 保存配置到数据库 + self.repository.save_config(&config).await?; + + // 重新初始化ComfyUI管理器以使用新配置 + let mut comfyui_manager = self.comfyui_manager.write().await; + *comfyui_manager = Some(Arc::new( + ComfyUIManager::new(config.clone())? + )); + + info!("配置更新成功,ComfyUI管理器已重新初始化"); + Ok(()) + } + /// 获取配置管理器 pub fn get_config_manager(&self) -> Arc { Arc::clone(&self.config_manager) diff --git a/apps/desktop/src-tauri/src/business/services/workflow_execution_service.rs b/apps/desktop/src-tauri/src/business/services/workflow_execution_service.rs index 6c62484..496f8e3 100644 --- a/apps/desktop/src-tauri/src/business/services/workflow_execution_service.rs +++ b/apps/desktop/src-tauri/src/business/services/workflow_execution_service.rs @@ -294,6 +294,7 @@ mod tests { use super::*; use serde_json::json; use chrono::Utc; + use crate::data::models::workflow_template::WorkflowType; fn create_test_template() -> WorkflowTemplate { WorkflowTemplate { diff --git a/apps/desktop/src-tauri/src/data/repositories/comfyui_repository.rs b/apps/desktop/src-tauri/src/data/repositories/comfyui_repository.rs index 17eaefa..27c4580 100644 --- a/apps/desktop/src-tauri/src/data/repositories/comfyui_repository.rs +++ b/apps/desktop/src-tauri/src/data/repositories/comfyui_repository.rs @@ -653,4 +653,73 @@ impl ComfyUIRepository { node_outputs, }) } + + /// 获取配置 + pub async fn get_config(&self) -> Result { + self.with_connection(|conn| { + let mut stmt = conn.prepare( + "SELECT base_url, timeout_seconds, retry_attempts, retry_delay_ms, + enable_websocket, enable_cache, max_concurrency, custom_headers + FROM comfyui_configs WHERE id = 'default'" + )?; + + let config_iter = stmt.query_map([], |row| { + let custom_headers_json: Option = row.get(7)?; + let custom_headers = if let Some(json) = custom_headers_json { + serde_json::from_str(&json).ok() + } else { + None + }; + + Ok(crate::data::models::comfyui::ComfyUIConfig { + base_url: row.get(0)?, + timeout_seconds: row.get(1)?, + retry_attempts: row.get(2)?, + retry_delay_ms: row.get(3)?, + enable_websocket: row.get(4)?, + enable_cache: row.get(5)?, + max_concurrency: row.get(6)?, + custom_headers, + }) + })?; + + for config in config_iter { + return Ok(config?); + } + + // 如果没有找到配置,返回默认配置 + Ok(crate::data::models::comfyui::ComfyUIConfig::default()) + }) + } + + /// 保存配置 + pub async fn save_config(&self, config: &crate::data::models::comfyui::ComfyUIConfig) -> Result<()> { + self.with_connection(|conn| { + let custom_headers_json = if let Some(headers) = &config.custom_headers { + Some(serde_json::to_string(headers)?) + } else { + None + }; + + conn.execute( + "INSERT OR REPLACE INTO comfyui_configs + (id, base_url, timeout_seconds, retry_attempts, retry_delay_ms, + enable_websocket, enable_cache, max_concurrency, custom_headers, updated_at) + VALUES ('default', ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, CURRENT_TIMESTAMP)", + params![ + config.base_url, + config.timeout_seconds, + config.retry_attempts, + config.retry_delay_ms, + config.enable_websocket, + config.enable_cache, + config.max_concurrency, + custom_headers_json + ], + )?; + + debug!("保存配置成功: {}", config.base_url); + Ok(()) + }) + } } diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 17bf6f1..c682bae 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -604,12 +604,12 @@ pub fn run() { commands::comfyui_v2_commands::comfyui_v2_health_check, commands::comfyui_v2_commands::comfyui_v2_get_system_info, commands::comfyui_v2_commands::comfyui_v2_get_queue_basic_status, - commands::comfyui_v2_commands::comfyui_v2_get_config, - commands::comfyui_v2_commands::comfyui_v2_update_config, - commands::comfyui_v2_commands::comfyui_v2_validate_config, - commands::comfyui_v2_commands::comfyui_v2_reset_config, - commands::comfyui_v2_commands::comfyui_v2_get_config_stats, - commands::comfyui_v2_commands::comfyui_v2_get_config_health, + // ComfyUI V2 配置命令 + commands::comfyui_v2_config_commands::comfyui_v2_get_config, + commands::comfyui_v2_config_commands::comfyui_v2_update_config, + commands::comfyui_v2_config_commands::comfyui_v2_validate_config, + commands::comfyui_v2_config_commands::comfyui_v2_reset_config, + commands::comfyui_v2_config_commands::comfyui_v2_test_connection, // ComfyUI V2 工作流命令 commands::comfyui_v2_commands::comfyui_v2_create_workflow, commands::comfyui_v2_commands::comfyui_v2_list_workflows, @@ -811,6 +811,24 @@ pub fn run() { // 不返回错误,让应用继续启动,只是记录错误 } + // 初始化ComfyUI V2管理器(异步) + let app_handle = app.handle().clone(); + tauri::async_runtime::spawn(async move { + let state: tauri::State = app_handle.state(); + + if let Err(e) = state.initialize_config_manager().await { + error!("初始化配置管理器失败: {}", e); + } else { + info!("配置管理器初始化成功"); + } + + if let Err(e) = state.initialize_comfyui_manager().await { + error!("初始化ComfyUI V2管理器失败: {}", e); + } else { + info!("ComfyUI V2管理器初始化成功"); + } + }); + Ok(()) }) .run(tauri::generate_context!()) @@ -828,6 +846,7 @@ mod tests { // 新的测试模块 pub mod test_utils; pub mod basic_tests; + pub mod comfyui_config_test; } diff --git a/apps/desktop/src-tauri/src/presentation/commands/comfyui_v2_commands.rs b/apps/desktop/src-tauri/src/presentation/commands/comfyui_v2_commands.rs index 5507dd4..86225bb 100644 --- a/apps/desktop/src-tauri/src/presentation/commands/comfyui_v2_commands.rs +++ b/apps/desktop/src-tauri/src/presentation/commands/comfyui_v2_commands.rs @@ -292,80 +292,7 @@ pub async fn comfyui_v2_get_queue_basic_status( -// ==================== 配置管理命令 ==================== - -/// 获取 ComfyUI 配置 -#[tauri::command] -pub async fn comfyui_v2_get_config( - state: State<'_, AppState>, -) -> Result { - info!("Command: comfyui_v2_get_config"); - - let config_manager = get_config_manager(&state).await?; - let config = config_manager.get_comfyui_config().await; - - Ok(config) -} - -/// 更新 ComfyUI 配置 -#[tauri::command] -pub async fn comfyui_v2_update_config( - config: ComfyUIV2Config, - state: State<'_, AppState>, -) -> Result { - info!("Command: comfyui_v2_update_config"); - - let config_manager = get_config_manager(&state).await?; - - // 转换为 ComfyUIConfig - let converted_config = convert_v2_config_to_config(config); - - match config_manager.update_comfyui_config(converted_config).await { - Ok(_) => { - info!("ComfyUI 配置更新成功"); - Ok("配置更新成功".to_string()) - } - Err(e) => { - error!("配置更新失败: {}", e); - Err(format!("配置更新失败: {}", e)) - } - } -} - -/// 验证配置 -#[tauri::command] -pub async fn comfyui_v2_validate_config( - config: ComfyUIV2Config, - state: State<'_, AppState>, -) -> Result { - info!("Command: comfyui_v2_validate_config"); - - // 转换为 ComfyUIConfig 进行验证 - let converted_config = convert_v2_config_to_config(config); - let validation = converted_config.validate(); - Ok(validation) -} - -/// 重置配置为默认值 -#[tauri::command] -pub async fn comfyui_v2_reset_config( - state: State<'_, AppState>, -) -> Result { - info!("Command: comfyui_v2_reset_config"); - - let config_manager = get_config_manager(&state).await?; - - match config_manager.reset_comfyui_config().await { - Ok(_) => { - info!("ComfyUI 配置重置成功"); - Ok("配置重置成功".to_string()) - } - Err(e) => { - error!("配置重置失败: {}", e); - Err(format!("配置重置失败: {}", e)) - } - } -} +// 配置管理命令已移动到 comfyui_v2_config_commands.rs // ==================== 统计和监控命令 ==================== diff --git a/apps/desktop/src-tauri/src/presentation/commands/comfyui_v2_config_commands.rs b/apps/desktop/src-tauri/src/presentation/commands/comfyui_v2_config_commands.rs new file mode 100644 index 0000000..0c21c52 --- /dev/null +++ b/apps/desktop/src-tauri/src/presentation/commands/comfyui_v2_config_commands.rs @@ -0,0 +1,231 @@ +use tauri::State; +use tracing::{info, error, debug}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +use crate::app_state::AppState; +use crate::data::models::comfyui::ComfyUIConfig; + +/// ComfyUI V2 配置请求 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ComfyUIV2ConfigRequest { + pub base_url: String, + pub timeout: Option, + pub retry_attempts: Option, + pub enable_cache: Option, + pub max_concurrency: Option, + pub websocket_url: Option, +} + +/// ComfyUI V2 配置响应 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ComfyUIV2ConfigResponse { + pub base_url: String, + pub timeout: u64, + pub retry_attempts: u32, + pub enable_cache: bool, + pub max_concurrency: u32, + pub websocket_url: String, + pub timeout_seconds: u64, + pub retry_delay_ms: u64, + pub enable_websocket: bool, + pub custom_headers: Option>, +} + +impl From for ComfyUIV2ConfigResponse { + fn from(config: ComfyUIConfig) -> Self { + // 从base_url推导websocket_url + let websocket_url = if config.base_url.starts_with("https://") { + config.base_url.replace("https://", "wss://") + "/ws" + } else { + config.base_url.replace("http://", "ws://") + "/ws" + }; + + Self { + base_url: config.base_url.clone(), + timeout: config.timeout_seconds * 1000, // 转换为毫秒 + retry_attempts: config.retry_attempts, + enable_cache: config.enable_cache, + max_concurrency: config.max_concurrency, + websocket_url, + timeout_seconds: config.timeout_seconds, + retry_delay_ms: config.retry_delay_ms, + enable_websocket: config.enable_websocket, + custom_headers: config.custom_headers, + } + } +} + +impl From for ComfyUIConfig { + fn from(request: ComfyUIV2ConfigRequest) -> Self { + Self { + base_url: request.base_url, + timeout_seconds: request.timeout.unwrap_or(30000) / 1000, // 转换为秒 + retry_attempts: request.retry_attempts.unwrap_or(3), + retry_delay_ms: 1000, // 默认1秒 + enable_websocket: true, // 默认启用 + enable_cache: request.enable_cache.unwrap_or(true), + max_concurrency: request.max_concurrency.unwrap_or(4), + custom_headers: None, + } + } +} + +/// 获取ComfyUI配置 +#[tauri::command] +pub async fn comfyui_v2_get_config( + state: State<'_, AppState>, +) -> Result { + info!("Command: comfyui_v2_get_config"); + + let service_manager = state.get_comfyui_manager().await + .map_err(|e| format!("获取ComfyUI管理器失败: {}", e))?; + + match service_manager.get_config().await { + Ok(config) => { + debug!("获取配置成功: {:?}", config); + Ok(ComfyUIV2ConfigResponse::from(config)) + }, + Err(e) => { + error!("获取配置失败: {}", e); + // 返回默认配置 + let default_config = ComfyUIConfig::default(); + Ok(ComfyUIV2ConfigResponse::from(default_config)) + } + } +} + +/// 更新ComfyUI配置 +#[tauri::command] +pub async fn comfyui_v2_update_config( + state: State<'_, AppState>, + config_request: ComfyUIV2ConfigRequest, +) -> Result { + info!("Command: comfyui_v2_update_config - {:?}", config_request); + + let service_manager = state.get_comfyui_manager().await + .map_err(|e| format!("获取ComfyUI管理器失败: {}", e))?; + + let config = ComfyUIConfig::from(config_request); + + match service_manager.update_config(config.clone()).await { + Ok(_) => { + info!("配置更新成功"); + Ok(ComfyUIV2ConfigResponse::from(config)) + }, + Err(e) => { + error!("配置更新失败: {}", e); + Err(format!("配置更新失败: {}", e)) + } + } +} + +/// 重置ComfyUI配置为默认值 +#[tauri::command] +pub async fn comfyui_v2_reset_config( + state: State<'_, AppState>, +) -> Result { + info!("Command: comfyui_v2_reset_config"); + + let service_manager = state.get_comfyui_manager().await + .map_err(|e| format!("获取ComfyUI管理器失败: {}", e))?; + + let default_config = ComfyUIConfig::default(); + + match service_manager.update_config(default_config.clone()).await { + Ok(_) => { + info!("配置重置成功"); + Ok(ComfyUIV2ConfigResponse::from(default_config)) + }, + Err(e) => { + error!("配置重置失败: {}", e); + Err(format!("配置重置失败: {}", e)) + } + } +} + +/// 验证ComfyUI配置 +#[tauri::command] +pub async fn comfyui_v2_validate_config( + config_request: ComfyUIV2ConfigRequest, +) -> Result { + info!("Command: comfyui_v2_validate_config - {:?}", config_request); + + // 基本验证 + if config_request.base_url.is_empty() { + return Err("API地址不能为空".to_string()); + } + + if !config_request.base_url.starts_with("http://") && !config_request.base_url.starts_with("https://") { + return Err("API地址必须以http://或https://开头".to_string()); + } + + if let Some(timeout) = config_request.timeout { + if timeout == 0 { + return Err("超时时间必须大于0".to_string()); + } + if timeout > 300000 { // 5分钟 + return Err("超时时间不能超过5分钟".to_string()); + } + } + + if let Some(retry_attempts) = config_request.retry_attempts { + if retry_attempts > 10 { + return Err("重试次数不能超过10次".to_string()); + } + } + + if let Some(max_concurrency) = config_request.max_concurrency { + if max_concurrency == 0 { + return Err("最大并发数必须大于0".to_string()); + } + if max_concurrency > 20 { + return Err("最大并发数不能超过20".to_string()); + } + } + + Ok(true) +} + +/// 测试ComfyUI连接 +#[tauri::command] +pub async fn comfyui_v2_test_connection( + config_request: ComfyUIV2ConfigRequest, +) -> Result { + info!("Command: comfyui_v2_test_connection - {:?}", config_request); + + // 首先验证配置 + comfyui_v2_validate_config(config_request.clone()).await?; + + // 创建临时的ComfyUI客户端进行连接测试 + let config = ComfyUIConfig::from(config_request); + + // 使用ComfyUI SDK进行连接测试 + let client_config = comfyui_sdk::types::ComfyUIClientConfig { + base_url: config.base_url.clone(), + timeout: Some(std::time::Duration::from_secs(config.timeout_seconds)), + retry_attempts: Some(config.retry_attempts), + retry_delay: Some(std::time::Duration::from_millis(config.retry_delay_ms)), + headers: config.custom_headers.clone(), + }; + + match comfyui_sdk::client::ComfyUIClient::new(client_config) { + Ok(client) => { + // 尝试获取系统信息来测试连接 + match client.get_system_stats().await { + Ok(_) => { + info!("连接测试成功"); + Ok(true) + }, + Err(e) => { + error!("连接测试失败: {}", e); + Err(format!("连接测试失败: {}", e)) + } + } + }, + Err(e) => { + error!("创建客户端失败: {}", e); + Err(format!("创建客户端失败: {}", e)) + } + } +} diff --git a/apps/desktop/src-tauri/src/presentation/commands/mod.rs b/apps/desktop/src-tauri/src/presentation/commands/mod.rs index 66ea421..6087fc1 100644 --- a/apps/desktop/src-tauri/src/presentation/commands/mod.rs +++ b/apps/desktop/src-tauri/src/presentation/commands/mod.rs @@ -49,6 +49,7 @@ pub mod comfyui_sdk_commands; // 新的基于 SDK 的 ComfyUI 命令 pub mod comfyui_v2_commands; +pub mod comfyui_v2_config_commands; pub mod comfyui_v2_template_commands; pub mod comfyui_v2_execution_commands; pub mod comfyui_v2_realtime_commands; diff --git a/apps/desktop/src-tauri/src/tests/comfyui_config_test.rs b/apps/desktop/src-tauri/src/tests/comfyui_config_test.rs new file mode 100644 index 0000000..0d878f2 --- /dev/null +++ b/apps/desktop/src-tauri/src/tests/comfyui_config_test.rs @@ -0,0 +1,142 @@ +#[cfg(test)] +mod comfyui_config_tests { + use super::*; + use crate::app_state::AppState; + use crate::data::models::comfyui::ComfyUIConfig; + use crate::infrastructure::database::Database; + use std::sync::Arc; + use tempfile::tempdir; + + async fn create_test_app_state() -> AppState { + let temp_dir = tempdir().unwrap(); + let db_path = temp_dir.path().join("test.db"); + + let database = Arc::new(Database::new(&db_path).unwrap()); + let state = AppState::new(); + + // 初始化数据库 + state.initialize_database().unwrap(); + + state + } + + #[tokio::test] + async fn test_config_manager_initialization() { + let state = create_test_app_state().await; + + // 测试配置管理器初始化 + let result = state.initialize_config_manager().await; + assert!(result.is_ok(), "配置管理器初始化应该成功"); + + // 测试获取配置管理器 + let config_manager = state.get_config_manager().await; + assert!(config_manager.is_ok(), "应该能够获取配置管理器"); + } + + #[tokio::test] + async fn test_comfyui_manager_initialization() { + let state = create_test_app_state().await; + + // 先初始化配置管理器 + state.initialize_config_manager().await.unwrap(); + + // 测试ComfyUI管理器初始化 + let result = state.initialize_comfyui_manager().await; + assert!(result.is_ok(), "ComfyUI管理器初始化应该成功"); + + // 测试获取ComfyUI管理器 + let comfyui_manager = state.get_comfyui_manager().await; + assert!(comfyui_manager.is_ok(), "应该能够获取ComfyUI管理器"); + } + + #[tokio::test] + async fn test_config_persistence() { + let state = create_test_app_state().await; + + // 初始化管理器 + state.initialize_config_manager().await.unwrap(); + state.initialize_comfyui_manager().await.unwrap(); + + let service_manager = state.get_comfyui_manager().await.unwrap(); + + // 创建测试配置 + let test_config = ComfyUIConfig { + base_url: "http://192.168.0.193:8188".to_string(), + timeout_seconds: 300, + retry_attempts: 3, + retry_delay_ms: 1000, + enable_websocket: true, + enable_cache: true, + max_concurrency: 4, + custom_headers: None, + }; + + // 保存配置 + let save_result = service_manager.update_config(test_config.clone()).await; + assert!(save_result.is_ok(), "配置保存应该成功"); + + // 读取配置 + let loaded_config = service_manager.get_config().await; + assert!(loaded_config.is_ok(), "配置读取应该成功"); + + let loaded_config = loaded_config.unwrap(); + assert_eq!(loaded_config.base_url, test_config.base_url, "base_url应该匹配"); + assert_eq!(loaded_config.timeout_seconds, test_config.timeout_seconds, "timeout_seconds应该匹配"); + assert_eq!(loaded_config.retry_attempts, test_config.retry_attempts, "retry_attempts应该匹配"); + } + + #[tokio::test] + async fn test_default_config_loading() { + let state = create_test_app_state().await; + + // 初始化管理器 + state.initialize_config_manager().await.unwrap(); + state.initialize_comfyui_manager().await.unwrap(); + + let service_manager = state.get_comfyui_manager().await.unwrap(); + + // 读取默认配置(数据库中没有配置时应该返回默认值) + let config = service_manager.get_config().await; + assert!(config.is_ok(), "应该能够获取默认配置"); + + let config = config.unwrap(); + assert_eq!(config.base_url, "http://localhost:8188", "默认base_url应该是localhost"); + assert_eq!(config.timeout_seconds, 300, "默认超时应该是300秒"); + assert_eq!(config.retry_attempts, 3, "默认重试次数应该是3"); + } + + #[tokio::test] + async fn test_config_validation() { + // 测试有效配置 + let valid_config = ComfyUIConfig { + base_url: "http://localhost:8188".to_string(), + timeout_seconds: 300, + retry_attempts: 3, + retry_delay_ms: 1000, + enable_websocket: true, + enable_cache: true, + max_concurrency: 4, + custom_headers: None, + }; + + let validation = valid_config.validate(); + assert!(validation.valid, "有效配置应该通过验证"); + assert!(validation.errors.is_empty(), "有效配置不应该有错误"); + + // 测试无效配置 + let invalid_config = ComfyUIConfig { + base_url: "invalid-url".to_string(), // 无效的URL + timeout_seconds: 0, // 无效的超时时间 + retry_attempts: 0, + retry_delay_ms: 0, + enable_websocket: true, + enable_cache: true, + max_concurrency: 0, // 无效的并发数 + custom_headers: None, + }; + + let validation = invalid_config.validate(); + assert!(!validation.valid, "无效配置应该验证失败"); + assert!(!validation.errors.is_empty(), "无效配置应该有错误信息"); + } +}