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:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
239
apps/desktop/src-tauri/src/data/models/comfyui.rs
Normal file
239
apps/desktop/src-tauri/src/data/models/comfyui.rs
Normal 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 },
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
325
apps/desktop/src-tauri/src/infrastructure/comfyui_service.rs
Normal file
325
apps/desktop/src-tauri/src/infrastructure/comfyui_service.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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!())
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
465
comfyui.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user