feat: 实现ComfyUI API服务集成

- 添加ComfyUI API数据模型 (comfyui.rs)
- 实现HTTP客户端服务层 (comfyui_service.rs)
- 创建Tauri命令层 (comfyui_commands.rs)
- 集成服务到应用状态管理
- 注册所有API命令到Tauri处理器
- 添加urlencoding依赖
- 支持8个API端点的完整功能

API端点包括:
- 工作流管理 (获取/发布/删除)
- 工作流执行和规范查询
- 服务器状态监控
- 文件列表管理
- 连接测试和配置管理

遵循四层架构模式: 数据模型  基础设施服务  表示命令  应用状态集成
This commit is contained in:
imeepos
2025-08-04 10:23:48 +08:00
parent fee8909b20
commit 42ae580034
11 changed files with 1439 additions and 3 deletions

7
Cargo.lock generated
View File

@@ -2501,6 +2501,7 @@ dependencies = [
"tree-sitter",
"tree-sitter-json",
"url",
"urlencoding",
"uuid",
"winapi",
]
@@ -5301,6 +5302,12 @@ dependencies = [
"serde",
]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "urlpattern"
version = "0.3.0"

View File

@@ -55,6 +55,7 @@ hmac = "0.12.1"
sha2 = "0.10.9"
hex = "0.4.3"
url = "2.5.4"
urlencoding = "2.1"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["sysinfoapi"] }

View File

@@ -11,6 +11,7 @@ use crate::infrastructure::database::Database;
use crate::infrastructure::performance::PerformanceMonitor;
use crate::infrastructure::event_bus::EventBusManager;
use crate::infrastructure::bowong_text_video_agent_service::BowongTextVideoAgentService;
use crate::infrastructure::comfyui_service::ComfyuiService;
/// 应用全局状态管理
@@ -28,7 +29,7 @@ pub struct AppState {
pub performance_monitor: Mutex<PerformanceMonitor>,
pub event_bus_manager: Arc<EventBusManager>,
pub bowong_text_video_agent_service: Mutex<Option<BowongTextVideoAgentService>>,
pub comfyui_service: Mutex<Option<ComfyuiService>>,
}
impl AppState {
@@ -46,7 +47,7 @@ impl AppState {
performance_monitor: Mutex::new(PerformanceMonitor::new()),
event_bus_manager: Arc::new(EventBusManager::new()),
bowong_text_video_agent_service: Mutex::new(None),
comfyui_service: Mutex::new(None),
}
}
@@ -223,7 +224,7 @@ impl AppState {
performance_monitor: Mutex::new(PerformanceMonitor::new()),
event_bus_manager: Arc::new(EventBusManager::new()),
bowong_text_video_agent_service: Mutex::new(None),
comfyui_service: Mutex::new(None),
};
// 不直接存储database而是在需要时返回传入的database
state
@@ -248,9 +249,44 @@ impl AppState {
.map_err(|e| anyhow::anyhow!("Failed to acquire lock for BowongTextVideoAgent service: {}", e))
}
/// 初始化 ComfyUI 服务
pub fn initialize_comfyui_service(&self, config: crate::data::models::comfyui::ComfyuiConfig) -> anyhow::Result<()> {
let service = ComfyuiService::new(config)?;
let mut comfyui_service = self.comfyui_service.lock()
.map_err(|e| anyhow::anyhow!("Failed to acquire lock for ComfyUI service: {}", e))?;
*comfyui_service = Some(service);
println!("✅ ComfyUI 服务初始化成功");
Ok(())
}
/// 获取 ComfyUI 服务的引用
pub fn get_comfyui_service(&self) -> Option<ComfyuiService> {
match self.comfyui_service.lock() {
Ok(guard) => guard.clone(),
Err(e) => {
eprintln!("Failed to acquire lock for ComfyUI service: {}", e);
None
}
}
}
/// 更新 ComfyUI 服务配置
pub async fn update_comfyui_config(&self, config: crate::data::models::comfyui::ComfyuiConfig) -> anyhow::Result<()> {
// 创建新的服务实例
let new_service = ComfyuiService::new(config)?;
// 更新服务
let mut comfyui_service = self.comfyui_service.lock()
.map_err(|e| anyhow::anyhow!("Failed to acquire lock for ComfyUI service: {}", e))?;
*comfyui_service = Some(new_service);
println!("✅ ComfyUI 服务配置更新成功");
Ok(())
}
}
impl Default for AppState {

View File

@@ -0,0 +1,239 @@
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
use std::collections::HashMap;
/// ComfyUI API 服务配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComfyuiConfig {
/// API 基础 URL
pub base_url: String,
/// 请求超时时间(秒)
pub timeout: Option<u64>,
/// 重试次数
pub retry_attempts: Option<u32>,
/// 是否启用缓存
pub enable_cache: Option<bool>,
/// 最大并发数
pub max_concurrency: Option<u32>,
}
impl Default for ComfyuiConfig {
fn default() -> Self {
Self {
base_url: "https://bowongai-dev--waas-demo-fastapi-webapp.modal.run".to_string(),
timeout: Some(30),
retry_attempts: Some(3),
enable_cache: Some(true),
max_concurrency: Some(8),
}
}
}
/// 工作流对象 - 用于 GET /api/workflow 响应
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Workflow {
/// 工作流的完整唯一名称,例如 'my_workflow [20250101120000]'
pub name: String,
/// 工作流的基础名称
pub base_name: Option<String>,
/// 工作流版本
pub version: Option<String>,
/// 工作流创建时间
pub created_at: Option<DateTime<Utc>>,
/// 工作流更新时间
pub updated_at: Option<DateTime<Utc>>,
/// 工作流描述
pub description: Option<String>,
/// 工作流配置数据
#[serde(flatten)]
pub additional_properties: HashMap<String, serde_json::Value>,
}
/// 发布工作流请求 - 用于 POST /api/workflow
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PublishWorkflowRequest {
/// 工作流名称
pub name: String,
/// 工作流配置数据
pub workflow_data: serde_json::Value,
/// 工作流描述
pub description: Option<String>,
/// 工作流版本
pub version: Option<String>,
}
/// 发布工作流响应
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PublishWorkflowResponse {
/// 操作是否成功
pub success: bool,
/// 响应消息
pub message: Option<String>,
/// 创建的工作流信息
pub workflow: Option<Workflow>,
}
/// 删除工作流响应
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteWorkflowResponse {
/// 操作是否成功
pub success: bool,
/// 响应消息
pub message: Option<String>,
/// 删除的工作流名称
pub deleted_workflow: Option<String>,
}
/// 执行工作流请求 - 用于 POST /api/run/
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecuteWorkflowRequest {
/// 工作流基础名称
pub base_name: String,
/// 工作流版本(可选)
pub version: Option<String>,
/// 请求数据
pub request_data: serde_json::Value,
}
/// 执行工作流响应
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecuteWorkflowResponse {
/// 执行任务ID
pub task_id: Option<String>,
/// 执行状态
pub status: Option<String>,
/// 响应消息
pub message: Option<String>,
/// 执行结果数据
pub result: Option<serde_json::Value>,
/// 错误信息
pub error: Option<String>,
}
/// 获取工作流规范请求 - 用于 GET /api/spec/
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetWorkflowSpecRequest {
/// 工作流基础名称
pub base_name: String,
/// 工作流版本(可选)
pub version: Option<String>,
}
/// 获取工作流规范响应
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetWorkflowSpecResponse {
/// 工作流规范数据
pub spec: serde_json::Value,
/// 工作流名称
pub name: Option<String>,
/// 工作流版本
pub version: Option<String>,
/// 规范描述
pub description: Option<String>,
}
/// 服务器队列详情
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerQueueDetails {
/// 正在运行的任务数量
pub running_count: i32,
/// 等待中的任务数量
pub pending_count: i32,
}
/// 服务器状态信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerStatus {
/// 服务器在配置列表中的索引
pub server_index: i32,
/// HTTP URL
pub http_url: String,
/// WebSocket URL
pub ws_url: String,
/// 输入目录路径
pub input_dir: String,
/// 输出目录路径
pub output_dir: String,
/// 服务器是否可达
pub is_reachable: bool,
/// 服务器是否空闲
pub is_free: bool,
/// 队列详情
pub queue_details: ServerQueueDetails,
}
/// 文件详情
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileDetails {
/// 文件名
pub name: String,
/// 文件大小KB
pub size_kb: f64,
/// 修改时间
pub modified_at: DateTime<Utc>,
}
/// 服务器文件信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerFiles {
/// 服务器在配置列表中的索引
pub server_index: i32,
/// HTTP URL
pub http_url: String,
/// 输入文件列表
pub input_files: Vec<FileDetails>,
/// 输出文件列表
pub output_files: Vec<FileDetails>,
}
/// API 根响应
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiRootResponse {
/// API 使用指南
pub guide: String,
/// API 版本
pub version: Option<String>,
/// 可用端点
pub endpoints: Option<Vec<String>>,
}
/// HTTP 验证错误详情
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationError {
/// 错误位置
pub loc: Vec<serde_json::Value>,
/// 错误消息
pub msg: String,
/// 错误类型
#[serde(rename = "type")]
pub error_type: String,
}
/// HTTP 验证错误响应
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HTTPValidationError {
/// 错误详情列表
pub detail: Vec<ValidationError>,
}
/// ComfyUI API 错误类型
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ComfyuiError {
/// 网络连接错误
NetworkError { message: String },
/// HTTP 错误
HttpError { status: u16, message: String },
/// 验证错误
ValidationError { errors: Vec<ValidationError> },
/// 服务器内部错误
ServerError { message: String },
/// 工作流不存在
WorkflowNotFound { workflow_name: String },
/// 配置错误
ConfigError { message: String },
/// 超时错误
TimeoutError { message: String },
/// 未知错误
UnknownError { message: String },
}

View File

@@ -30,3 +30,4 @@ pub mod outfit_image;
pub mod outfit_photo_generation;
pub mod video_generation_record;
pub mod hedra_lipsync_record;
pub mod comfyui;

View File

@@ -0,0 +1,325 @@
use crate::data::models::comfyui::*;
use anyhow::{anyhow, Result};
use reqwest::{Client, Response};
use serde_json::Value;
use std::time::Duration;
use tracing::{debug, error, info, warn};
/// ComfyUI API 服务
///
/// 提供对 ComfyUI Workflow Service & Management API 的完整封装
/// 基于 OpenAPI 3.1.0 规范实现所有端点
#[derive(Debug, Clone)]
pub struct ComfyuiService {
/// HTTP 客户端
client: Client,
/// 服务配置
config: ComfyuiConfig,
}
impl ComfyuiService {
/// 创建新的 ComfyUI 服务实例
pub fn new(config: ComfyuiConfig) -> Result<Self> {
let timeout = Duration::from_secs(config.timeout.unwrap_or(30));
let client = Client::builder()
.timeout(timeout)
.build()
.map_err(|e| anyhow!("Failed to create HTTP client: {}", e))?;
info!("ComfyUI service initialized with base URL: {}", config.base_url);
Ok(Self { client, config })
}
/// 获取所有工作流
///
/// GET /api/workflow
/// 返回所有已发布的工作流列表
pub async fn get_all_workflows(&self) -> Result<Vec<Workflow>> {
let url = format!("{}/api/workflow", self.config.base_url.trim_end_matches('/'));
debug!("Getting all workflows from: {}", url);
let response = self.execute_get_request(&url).await?;
let workflows: Vec<Workflow> = response.json().await
.map_err(|e| anyhow!("Failed to parse workflows response: {}", e))?;
info!("Retrieved {} workflows", workflows.len());
Ok(workflows)
}
/// 发布工作流
///
/// POST /api/workflow
/// 发布新的工作流或更新现有工作流
pub async fn publish_workflow(&self, request: PublishWorkflowRequest) -> Result<PublishWorkflowResponse> {
let url = format!("{}/api/workflow", self.config.base_url.trim_end_matches('/'));
debug!("Publishing workflow '{}' to: {}", request.name, url);
let response = self.execute_post_request(&url, &request).await?;
// 根据 OpenAPI 规范,成功响应状态码是 201
if response.status().as_u16() == 201 {
// 响应体可能为空,创建成功响应
let workflow_response = PublishWorkflowResponse {
success: true,
message: Some("Workflow published successfully".to_string()),
workflow: None, // API 规范中响应体为空
};
info!("Workflow '{}' published successfully", request.name);
Ok(workflow_response)
} else {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
Err(anyhow!("Failed to publish workflow: HTTP {} - {}", status, error_text))
}
}
/// 删除工作流
///
/// DELETE /api/workflow/{workflow_name}
/// 删除指定的工作流
pub async fn delete_workflow(&self, workflow_name: &str) -> Result<DeleteWorkflowResponse> {
let encoded_name = urlencoding::encode(workflow_name);
let url = format!("{}/api/workflow/{}", self.config.base_url.trim_end_matches('/'), encoded_name);
debug!("Deleting workflow '{}' from: {}", workflow_name, url);
let response = self.execute_delete_request(&url).await?;
if response.status().is_success() {
let delete_response = DeleteWorkflowResponse {
success: true,
message: Some("Workflow deleted successfully".to_string()),
deleted_workflow: Some(workflow_name.to_string()),
};
info!("Workflow '{}' deleted successfully", workflow_name);
Ok(delete_response)
} else {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
Err(anyhow!("Failed to delete workflow: HTTP {} - {}", status, error_text))
}
}
/// 执行工作流
///
/// POST /api/run/
/// 执行指定的工作流
pub async fn execute_workflow(&self, request: ExecuteWorkflowRequest) -> Result<ExecuteWorkflowResponse> {
let mut url = format!("{}/api/run/", self.config.base_url.trim_end_matches('/'));
// 添加查询参数
url.push_str(&format!("?base_name={}", urlencoding::encode(&request.base_name)));
if let Some(version) = &request.version {
url.push_str(&format!("&version={}", urlencoding::encode(version)));
}
debug!("Executing workflow '{}' at: {}", request.base_name, url);
let response = self.execute_post_request(&url, &request.request_data).await?;
if response.status().is_success() {
// 尝试解析响应,如果失败则返回基本成功响应
let result: Value = response.json().await
.unwrap_or_else(|_| serde_json::json!({}));
let execute_response = ExecuteWorkflowResponse {
task_id: result.get("task_id").and_then(|v| v.as_str()).map(String::from),
status: result.get("status").and_then(|v| v.as_str()).map(String::from),
message: Some("Workflow execution started".to_string()),
result: Some(result),
error: None,
};
info!("Workflow '{}' execution started", request.base_name);
Ok(execute_response)
} else {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
let execute_response = ExecuteWorkflowResponse {
task_id: None,
status: Some("failed".to_string()),
message: None,
result: None,
error: Some(format!("HTTP {} - {}", status, error_text)),
};
warn!("Workflow '{}' execution failed: {}", request.base_name, error_text);
Ok(execute_response)
}
}
/// 获取工作流规范
///
/// GET /api/spec/
/// 获取指定工作流的规范信息
pub async fn get_workflow_spec(&self, request: GetWorkflowSpecRequest) -> Result<GetWorkflowSpecResponse> {
let mut url = format!("{}/api/spec/", self.config.base_url.trim_end_matches('/'));
// 添加查询参数
url.push_str(&format!("?base_name={}", urlencoding::encode(&request.base_name)));
if let Some(version) = &request.version {
url.push_str(&format!("&version={}", urlencoding::encode(version)));
}
debug!("Getting workflow spec for '{}' from: {}", request.base_name, url);
let response = self.execute_get_request(&url).await?;
let spec: Value = response.json().await
.map_err(|e| anyhow!("Failed to parse workflow spec response: {}", e))?;
let spec_response = GetWorkflowSpecResponse {
spec: spec.clone(),
name: Some(request.base_name.clone()),
version: request.version.clone(),
description: spec.get("description").and_then(|v| v.as_str()).map(String::from),
};
info!("Retrieved workflow spec for '{}'", request.base_name);
Ok(spec_response)
}
/// 获取服务器状态
///
/// GET /api/servers/status
/// 获取所有已配置的ComfyUI服务器的配置信息和实时状态
pub async fn get_servers_status(&self) -> Result<Vec<ServerStatus>> {
let url = format!("{}/api/servers/status", self.config.base_url.trim_end_matches('/'));
debug!("Getting servers status from: {}", url);
let response = self.execute_get_request(&url).await?;
let servers: Vec<ServerStatus> = response.json().await
.map_err(|e| anyhow!("Failed to parse servers status response: {}", e))?;
info!("Retrieved status for {} servers", servers.len());
Ok(servers)
}
/// 获取服务器文件列表
///
/// GET /api/servers/{server_index}/files
/// 获取指定ComfyUI服务器的输入和输出文件夹中的文件列表
pub async fn list_server_files(&self, server_index: i32) -> Result<ServerFiles> {
let url = format!("{}/api/servers/{}/files", self.config.base_url.trim_end_matches('/'), server_index);
debug!("Getting server files for server {} from: {}", server_index, url);
let response = self.execute_get_request(&url).await?;
let files: ServerFiles = response.json().await
.map_err(|e| anyhow!("Failed to parse server files response: {}", e))?;
info!("Retrieved file list for server {}: {} input files, {} output files",
server_index, files.input_files.len(), files.output_files.len());
Ok(files)
}
/// 获取 API 根信息
///
/// GET /
/// 提供一个API的快速使用指南
pub async fn get_api_root(&self) -> Result<ApiRootResponse> {
let url = self.config.base_url.trim_end_matches('/').to_string();
debug!("Getting API root info from: {}", url);
let response = self.execute_get_request(&url).await?;
// 尝试解析为结构化响应,如果失败则返回基本信息
let result: Value = response.json().await
.unwrap_or_else(|_| serde_json::json!({"guide": "ComfyUI API"}));
let root_response = ApiRootResponse {
guide: result.get("guide").and_then(|v| v.as_str())
.unwrap_or("ComfyUI Workflow Service & Management API").to_string(),
version: result.get("version").and_then(|v| v.as_str()).map(String::from),
endpoints: result.get("endpoints").and_then(|v| v.as_array())
.map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect()),
};
info!("Retrieved API root information");
Ok(root_response)
}
// ========== 私有辅助方法 ==========
/// 执行 GET 请求
async fn execute_get_request(&self, url: &str) -> Result<Response> {
let response = self.client.get(url).send().await
.map_err(|e| anyhow!("GET request failed: {}", e))?;
Ok(response)
}
/// 执行 POST 请求
async fn execute_post_request<T: serde::Serialize>(&self, url: &str, body: &T) -> Result<Response> {
let response = self.client.post(url).json(body).send().await
.map_err(|e| anyhow!("POST request failed: {}", e))?;
Ok(response)
}
/// 执行 DELETE 请求
async fn execute_delete_request(&self, url: &str) -> Result<Response> {
let response = self.client.delete(url).send().await
.map_err(|e| anyhow!("DELETE request failed: {}", e))?;
Ok(response)
}
/// 验证配置
pub fn validate_config(&self) -> Result<()> {
if self.config.base_url.is_empty() {
return Err(anyhow!("Base URL cannot be empty"));
}
if !self.config.base_url.starts_with("http://") && !self.config.base_url.starts_with("https://") {
return Err(anyhow!("Base URL must start with http:// or https://"));
}
if let Some(timeout) = self.config.timeout {
if timeout == 0 {
return Err(anyhow!("Timeout must be greater than 0"));
}
}
if let Some(retry_attempts) = self.config.retry_attempts {
if retry_attempts > 10 {
warn!("Retry attempts is very high ({}), consider reducing it", retry_attempts);
}
}
Ok(())
}
/// 获取服务配置
pub fn get_config(&self) -> &ComfyuiConfig {
&self.config
}
/// 更新服务配置
pub fn update_config(&mut self, config: ComfyuiConfig) -> Result<()> {
// 验证新配置
let temp_service = Self::new(config.clone())?;
temp_service.validate_config()?;
// 更新配置和客户端
self.config = config;
let timeout = Duration::from_secs(self.config.timeout.unwrap_or(30));
self.client = Client::builder()
.timeout(timeout)
.build()
.map_err(|e| anyhow!("Failed to create HTTP client: {}", e))?;
info!("ComfyUI service configuration updated");
Ok(())
}
/// 测试连接
pub async fn test_connection(&self) -> Result<bool> {
match self.get_api_root().await {
Ok(_) => {
info!("ComfyUI service connection test successful");
Ok(true)
}
Err(e) => {
error!("ComfyUI service connection test failed: {}", e);
Ok(false)
}
}
}
}

View File

@@ -19,3 +19,4 @@ pub mod image_editing_service;
pub mod tolerant_json_parser;
pub mod markdown_parser;
pub mod bowong_text_video_agent_service;
pub mod comfyui_service;

View File

@@ -560,6 +560,18 @@ pub fn run() {
commands::bowong_text_video_agent_commands::bowong_cancel_tasks,
commands::bowong_text_video_agent_commands::bowong_batch_query_task_status,
commands::bowong_text_video_agent_commands::bowong_wait_for_task_completion,
// ComfyUI API 命令
commands::comfyui_commands::comfyui_get_all_workflows,
commands::comfyui_commands::comfyui_publish_workflow,
commands::comfyui_commands::comfyui_delete_workflow,
commands::comfyui_commands::comfyui_execute_workflow,
commands::comfyui_commands::comfyui_get_workflow_spec,
commands::comfyui_commands::comfyui_get_servers_status,
commands::comfyui_commands::comfyui_list_server_files,
commands::comfyui_commands::comfyui_get_api_root,
commands::comfyui_commands::comfyui_test_connection,
commands::comfyui_commands::comfyui_get_config,
commands::comfyui_commands::comfyui_update_config,
// Hedra 口型合成命令
commands::bowong_text_video_agent_commands::hedra_upload_file,
commands::bowong_text_video_agent_commands::hedra_submit_task,
@@ -627,6 +639,20 @@ pub fn run() {
// 不返回错误,让应用继续启动,只是记录错误
}
// 初始化 ComfyUI 服务
let comfyui_config = data::models::comfyui::ComfyuiConfig {
base_url: "https://bowongai-dev--waas-demo-fastapi-webapp.modal.run".to_string(),
timeout: Some(30),
retry_attempts: Some(3),
enable_cache: Some(true),
max_concurrency: Some(8),
};
if let Err(e) = state.initialize_comfyui_service(comfyui_config) {
eprintln!("Failed to initialize ComfyUI service: {}", e);
// 不返回错误,让应用继续启动,只是记录错误
}
Ok(())
})
.run(tauri::generate_context!())

View File

@@ -0,0 +1,334 @@
use crate::app_state::AppState;
use crate::data::models::comfyui::*;
use anyhow::Result;
use tauri::State;
use tracing::{error, info};
/// 获取所有工作流
///
/// 前端调用示例:
/// ```typescript
/// const workflows = await invoke('comfyui_get_all_workflows');
/// ```
#[tauri::command]
pub async fn comfyui_get_all_workflows(
state: State<'_, AppState>,
) -> Result<Vec<Workflow>, String> {
info!("Command: comfyui_get_all_workflows");
let app_state = state.inner();
let comfyui_service = app_state.get_comfyui_service()
.ok_or_else(|| "ComfyUI service not initialized".to_string())?;
match comfyui_service.get_all_workflows().await {
Ok(workflows) => {
info!("Successfully retrieved {} workflows", workflows.len());
Ok(workflows)
}
Err(e) => {
error!("Failed to get workflows: {}", e);
Err(format!("Failed to get workflows: {}", e))
}
}
}
/// 发布工作流
///
/// 前端调用示例:
/// ```typescript
/// const request = {
/// name: "my_workflow",
/// workflow_data: { /* workflow config */ },
/// description: "My test workflow",
/// version: "1.0"
/// };
/// const response = await invoke('comfyui_publish_workflow', { request });
/// ```
#[tauri::command]
pub async fn comfyui_publish_workflow(
request: PublishWorkflowRequest,
state: State<'_, AppState>,
) -> Result<PublishWorkflowResponse, String> {
info!("Command: comfyui_publish_workflow - {}", request.name);
let app_state = state.inner();
let comfyui_service = app_state.get_comfyui_service()
.ok_or_else(|| "ComfyUI service not initialized".to_string())?;
match comfyui_service.publish_workflow(request).await {
Ok(response) => {
info!("Successfully published workflow");
Ok(response)
}
Err(e) => {
error!("Failed to publish workflow: {}", e);
Err(format!("Failed to publish workflow: {}", e))
}
}
}
/// 删除工作流
///
/// 前端调用示例:
/// ```typescript
/// const response = await invoke('comfyui_delete_workflow', {
/// workflowName: "my_workflow [20250101120000]"
/// });
/// ```
#[tauri::command]
pub async fn comfyui_delete_workflow(
workflow_name: String,
state: State<'_, AppState>,
) -> Result<DeleteWorkflowResponse, String> {
info!("Command: comfyui_delete_workflow - {}", workflow_name);
let app_state = state.inner();
let comfyui_service = app_state.get_comfyui_service()
.ok_or_else(|| "ComfyUI service not initialized".to_string())?;
match comfyui_service.delete_workflow(&workflow_name).await {
Ok(response) => {
info!("Successfully deleted workflow: {}", workflow_name);
Ok(response)
}
Err(e) => {
error!("Failed to delete workflow {}: {}", workflow_name, e);
Err(format!("Failed to delete workflow: {}", e))
}
}
}
/// 执行工作流
///
/// 前端调用示例:
/// ```typescript
/// const request = {
/// base_name: "my_workflow",
/// version: "1.0", // 可选
/// request_data: { /* execution parameters */ }
/// };
/// const response = await invoke('comfyui_execute_workflow', { request });
/// ```
#[tauri::command]
pub async fn comfyui_execute_workflow(
request: ExecuteWorkflowRequest,
state: State<'_, AppState>,
) -> Result<ExecuteWorkflowResponse, String> {
info!("Command: comfyui_execute_workflow - {}", request.base_name);
let app_state = state.inner();
let comfyui_service = app_state.get_comfyui_service()
.ok_or_else(|| "ComfyUI service not initialized".to_string())?;
match comfyui_service.execute_workflow(request).await {
Ok(response) => {
info!("Successfully executed workflow");
Ok(response)
}
Err(e) => {
error!("Failed to execute workflow: {}", e);
Err(format!("Failed to execute workflow: {}", e))
}
}
}
/// 获取工作流规范
///
/// 前端调用示例:
/// ```typescript
/// const request = {
/// base_name: "my_workflow",
/// version: "1.0" // 可选
/// };
/// const response = await invoke('comfyui_get_workflow_spec', { request });
/// ```
#[tauri::command]
pub async fn comfyui_get_workflow_spec(
request: GetWorkflowSpecRequest,
state: State<'_, AppState>,
) -> Result<GetWorkflowSpecResponse, String> {
info!("Command: comfyui_get_workflow_spec - {}", request.base_name);
let app_state = state.inner();
let comfyui_service = app_state.get_comfyui_service()
.ok_or_else(|| "ComfyUI service not initialized".to_string())?;
match comfyui_service.get_workflow_spec(request).await {
Ok(response) => {
info!("Successfully retrieved workflow spec");
Ok(response)
}
Err(e) => {
error!("Failed to get workflow spec: {}", e);
Err(format!("Failed to get workflow spec: {}", e))
}
}
}
/// 获取服务器状态
///
/// 前端调用示例:
/// ```typescript
/// const servers = await invoke('comfyui_get_servers_status');
/// ```
#[tauri::command]
pub async fn comfyui_get_servers_status(
state: State<'_, AppState>,
) -> Result<Vec<ServerStatus>, String> {
info!("Command: comfyui_get_servers_status");
let app_state = state.inner();
let comfyui_service = app_state.get_comfyui_service()
.ok_or_else(|| "ComfyUI service not initialized".to_string())?;
match comfyui_service.get_servers_status().await {
Ok(servers) => {
info!("Successfully retrieved status for {} servers", servers.len());
Ok(servers)
}
Err(e) => {
error!("Failed to get servers status: {}", e);
Err(format!("Failed to get servers status: {}", e))
}
}
}
/// 获取服务器文件列表
///
/// 前端调用示例:
/// ```typescript
/// const files = await invoke('comfyui_list_server_files', { serverIndex: 0 });
/// ```
#[tauri::command]
pub async fn comfyui_list_server_files(
server_index: i32,
state: State<'_, AppState>,
) -> Result<ServerFiles, String> {
info!("Command: comfyui_list_server_files - server {}", server_index);
let app_state = state.inner();
let comfyui_service = app_state.get_comfyui_service()
.ok_or_else(|| "ComfyUI service not initialized".to_string())?;
match comfyui_service.list_server_files(server_index).await {
Ok(files) => {
info!("Successfully retrieved files for server {}", server_index);
Ok(files)
}
Err(e) => {
error!("Failed to get server files for server {}: {}", server_index, e);
Err(format!("Failed to get server files: {}", e))
}
}
}
/// 获取 API 根信息
///
/// 前端调用示例:
/// ```typescript
/// const apiInfo = await invoke('comfyui_get_api_root');
/// ```
#[tauri::command]
pub async fn comfyui_get_api_root(
state: State<'_, AppState>,
) -> Result<ApiRootResponse, String> {
info!("Command: comfyui_get_api_root");
let app_state = state.inner();
let comfyui_service = app_state.get_comfyui_service()
.ok_or_else(|| "ComfyUI service not initialized".to_string())?;
match comfyui_service.get_api_root().await {
Ok(response) => {
info!("Successfully retrieved API root information");
Ok(response)
}
Err(e) => {
error!("Failed to get API root: {}", e);
Err(format!("Failed to get API root: {}", e))
}
}
}
/// 测试 ComfyUI 服务连接
///
/// 前端调用示例:
/// ```typescript
/// const isConnected = await invoke('comfyui_test_connection');
/// ```
#[tauri::command]
pub async fn comfyui_test_connection(
state: State<'_, AppState>,
) -> Result<bool, String> {
info!("Command: comfyui_test_connection");
let app_state = state.inner();
let comfyui_service = app_state.get_comfyui_service()
.ok_or_else(|| "ComfyUI service not initialized".to_string())?;
match comfyui_service.test_connection().await {
Ok(is_connected) => {
info!("Connection test result: {}", is_connected);
Ok(is_connected)
}
Err(e) => {
error!("Failed to test connection: {}", e);
Err(format!("Failed to test connection: {}", e))
}
}
}
/// 获取 ComfyUI 服务配置
///
/// 前端调用示例:
/// ```typescript
/// const config = await invoke('comfyui_get_config');
/// ```
#[tauri::command]
pub async fn comfyui_get_config(
state: State<'_, AppState>,
) -> Result<ComfyuiConfig, String> {
info!("Command: comfyui_get_config");
let app_state = state.inner();
let comfyui_service = app_state.get_comfyui_service()
.ok_or_else(|| "ComfyUI service not initialized".to_string())?;
let config = comfyui_service.get_config().clone();
info!("Successfully retrieved ComfyUI configuration");
Ok(config)
}
/// 更新 ComfyUI 服务配置
///
/// 前端调用示例:
/// ```typescript
/// const newConfig = {
/// base_url: "https://new-api-url.com",
/// timeout: 60,
/// retry_attempts: 5,
/// enable_cache: true,
/// max_concurrency: 10
/// };
/// const success = await invoke('comfyui_update_config', { config: newConfig });
/// ```
#[tauri::command]
pub async fn comfyui_update_config(
config: ComfyuiConfig,
state: State<'_, AppState>,
) -> Result<bool, String> {
info!("Command: comfyui_update_config");
let app_state = state.inner();
match app_state.update_comfyui_config(config).await {
Ok(_) => {
info!("Successfully updated ComfyUI configuration");
Ok(true)
}
Err(e) => {
error!("Failed to update ComfyUI configuration: {}", e);
Err(format!("Failed to update configuration: {}", e))
}
}
}

View File

@@ -43,3 +43,4 @@ pub mod error_handling_commands;
pub mod volcano_video_commands;
pub mod bowong_text_video_agent_commands;
pub mod hedra_lipsync_commands;
pub mod comfyui_commands;

465
comfyui.json Normal file
View File

@@ -0,0 +1,465 @@
{
"openapi": "3.1.0",
"info": {
"title": "ComfyUI Workflow Service & Management API",
"version": "0.1.0"
},
"paths": {
"/api/workflow": {
"get": {
"summary": "Get All Workflows Endpoint",
"operationId": "get_all_workflows_endpoint_api_workflow_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array",
"title": "Response Get All Workflows Endpoint Api Workflow Get"
}
}
}
}
}
},
"post": {
"summary": "Publish Workflow Endpoint",
"operationId": "publish_workflow_endpoint_api_workflow_post",
"responses": {
"201": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
},
"/api/workflow/{workflow_name}": {
"delete": {
"summary": "Delete Workflow Endpoint",
"operationId": "delete_workflow_endpoint_api_workflow__workflow_name__delete",
"parameters": [
{
"name": "workflow_name",
"in": "path",
"required": true,
"schema": {
"type": "string",
"description": "The full, unique name of the workflow to delete, e.g., 'my_workflow [20250101120000]'",
"title": "Workflow Name"
},
"description": "The full, unique name of the workflow to delete, e.g., 'my_workflow [20250101120000]'"
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/run/": {
"post": {
"summary": "Execute Workflow Endpoint",
"operationId": "execute_workflow_endpoint_api_run__post",
"parameters": [
{
"name": "base_name",
"in": "query",
"required": true,
"schema": {
"type": "string",
"title": "Base Name"
}
},
{
"name": "version",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Version"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true,
"title": "Request Data Raw"
}
}
}
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/spec/": {
"get": {
"summary": "Get Workflow Spec Endpoint",
"operationId": "get_workflow_spec_endpoint_api_spec__get",
"parameters": [
{
"name": "base_name",
"in": "query",
"required": true,
"schema": {
"type": "string",
"title": "Base Name"
}
},
{
"name": "version",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Version"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/servers/status": {
"get": {
"tags": [
"Server Monitoring"
],
"summary": "Get Servers Status Endpoint",
"description": "获取所有已配置的ComfyUI服务器的配置信息和实时状态。",
"operationId": "get_servers_status_endpoint_api_servers_status_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/ServerStatus"
},
"type": "array",
"title": "Response Get Servers Status Endpoint Api Servers Status Get"
}
}
}
}
}
}
},
"/api/servers/{server_index}/files": {
"get": {
"tags": [
"Server Monitoring"
],
"summary": "List Server Files Endpoint",
"description": "获取指定ComfyUI服务器的输入和输出文件夹中的文件列表。",
"operationId": "list_server_files_endpoint_api_servers__server_index__files_get",
"parameters": [
{
"name": "server_index",
"in": "path",
"required": true,
"schema": {
"type": "integer",
"minimum": 0,
"description": "服务器在配置列表中的索引",
"title": "Server Index"
},
"description": "服务器在配置列表中的索引"
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ServerFiles"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/": {
"get": {
"summary": "Read Root",
"description": "提供一个API的快速使用指南。",
"operationId": "read_root__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
}
},
"components": {
"schemas": {
"FileDetails": {
"properties": {
"name": {
"type": "string",
"title": "Name"
},
"size_kb": {
"type": "number",
"title": "Size Kb"
},
"modified_at": {
"type": "string",
"format": "date-time",
"title": "Modified At"
}
},
"type": "object",
"required": [
"name",
"size_kb",
"modified_at"
],
"title": "FileDetails"
},
"HTTPValidationError": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
},
"ServerFiles": {
"properties": {
"server_index": {
"type": "integer",
"title": "Server Index"
},
"http_url": {
"type": "string",
"title": "Http Url"
},
"input_files": {
"items": {
"$ref": "#/components/schemas/FileDetails"
},
"type": "array",
"title": "Input Files"
},
"output_files": {
"items": {
"$ref": "#/components/schemas/FileDetails"
},
"type": "array",
"title": "Output Files"
}
},
"type": "object",
"required": [
"server_index",
"http_url",
"input_files",
"output_files"
],
"title": "ServerFiles"
},
"ServerQueueDetails": {
"properties": {
"running_count": {
"type": "integer",
"title": "Running Count"
},
"pending_count": {
"type": "integer",
"title": "Pending Count"
}
},
"type": "object",
"required": [
"running_count",
"pending_count"
],
"title": "ServerQueueDetails"
},
"ServerStatus": {
"properties": {
"server_index": {
"type": "integer",
"title": "Server Index"
},
"http_url": {
"type": "string",
"title": "Http Url"
},
"ws_url": {
"type": "string",
"title": "Ws Url"
},
"input_dir": {
"type": "string",
"title": "Input Dir"
},
"output_dir": {
"type": "string",
"title": "Output Dir"
},
"is_reachable": {
"type": "boolean",
"title": "Is Reachable"
},
"is_free": {
"type": "boolean",
"title": "Is Free"
},
"queue_details": {
"$ref": "#/components/schemas/ServerQueueDetails"
}
},
"type": "object",
"required": [
"server_index",
"http_url",
"ws_url",
"input_dir",
"output_dir",
"is_reachable",
"is_free",
"queue_details"
],
"title": "ServerStatus"
},
"ValidationError": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
}
}
}
}