feat: add uni-comfyui-sdk - Rust SDK for ComfyUI API
- Complete Rust SDK implementation based on OpenAPI specification - All 23 endpoints implemented with type-safe interfaces - Comprehensive error handling and async support - Full documentation and examples included - Client renamed to UniComfyUIClient for clear identification
This commit is contained in:
20
cargos/uni-comfyui-sdk/.gitignore
vendored
Normal file
20
cargos/uni-comfyui-sdk/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# Rust
|
||||
/target/
|
||||
Cargo.lock
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
23
cargos/uni-comfyui-sdk/Cargo.toml
Normal file
23
cargos/uni-comfyui-sdk/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "uni-comfyui-sdk"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Rust SDK for ComfyUI Workflow Service & Management API"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/your-repo/uni-comfyui-sdk"
|
||||
keywords = ["comfyui", "api", "sdk", "workflow"]
|
||||
categories = ["api-bindings", "web-programming::http-client"]
|
||||
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
thiserror = "1.0"
|
||||
url = "2.4"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-test = "0.4"
|
||||
124
cargos/uni-comfyui-sdk/README.md
Normal file
124
cargos/uni-comfyui-sdk/README.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# uni-comfyui-sdk
|
||||
|
||||
A Rust SDK for ComfyUI Workflow Service & Management API.
|
||||
|
||||
## Features
|
||||
|
||||
- **Status Management**: Get metrics, server status, and file listings
|
||||
- **Workflow Management**: Create, retrieve, and delete workflows
|
||||
- **Workflow Execution**: Run workflows and monitor their status
|
||||
- **Server Management**: Register, unregister, and manage ComfyUI servers
|
||||
- **Monitoring**: Health checks, system stats, and task monitoring
|
||||
- **Type Safety**: Strongly typed API with comprehensive error handling
|
||||
|
||||
## Installation
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
uni-comfyui-sdk = "0.1.0"
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```rust
|
||||
use uni_comfyui_sdk::{UniComfyUIClient, Result};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// Create a client
|
||||
let client = UniComfyUIClient::new("http://localhost:8000")?;
|
||||
|
||||
// Get system metrics
|
||||
let metrics = client.get_metrics().await?;
|
||||
println!("Metrics: {:#?}", metrics);
|
||||
|
||||
// Get all workflows
|
||||
let workflows = client.get_all_workflows().await?;
|
||||
println!("Found {} workflows", workflows.len());
|
||||
|
||||
// Run a workflow
|
||||
let mut data = HashMap::new();
|
||||
data.insert("input".to_string(), serde_json::json!("test input"));
|
||||
let result = client.run_workflow("my_workflow", None, data).await?;
|
||||
println!("Workflow result: {:#?}", result);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## API Coverage
|
||||
|
||||
### Status Endpoints
|
||||
- `get_metrics()` - Get queue status overview
|
||||
- `get_servers_status()` - Get all configured ComfyUI servers status
|
||||
- `list_server_files(server_index)` - List files in server input/output folders
|
||||
|
||||
### Workflow Endpoints
|
||||
- `get_all_workflows()` - Get all workflows
|
||||
- `publish_workflow(data)` - Publish a new workflow
|
||||
- `delete_workflow(workflow_name)` - Delete a workflow
|
||||
- `get_one_workflow(base_name, version)` - Get workflow specification
|
||||
|
||||
### Run Endpoints
|
||||
- `run_workflow(name, version, data)` - Execute workflow asynchronously
|
||||
- `get_run_status(workflow_run_id)` - Get workflow execution status
|
||||
|
||||
### RunX Endpoints
|
||||
- `model_with_multi_dress(version, data)` - Model with multiple dresses workflow
|
||||
- `model_with_multi_dress_json_schema()` - Get workflow definition
|
||||
|
||||
### Monitor Endpoints
|
||||
- `monitor_dashboard()` - Get monitoring dashboard HTML
|
||||
- `get_system_stats()` - Get system statistics
|
||||
- `get_task_stats()` - Get task statistics
|
||||
- `get_monitor_server_status()` - Get server status information
|
||||
- `get_recent_tasks(limit)` - Get recent tasks list
|
||||
- `health_check()` - Health check endpoint
|
||||
|
||||
### ComfyUI Server Management Endpoints
|
||||
- `register_server(request)` - Register ComfyUI server
|
||||
- `update_heartbeat(request)` - Update server heartbeat
|
||||
- `unregister_server(request)` - Unregister ComfyUI server
|
||||
- `get_server_status(server_name)` - Get specific server status
|
||||
- `list_all_servers()` - Get all servers list
|
||||
- `get_system_health()` - Get system overall health status
|
||||
- `force_health_check()` - Force health check execution
|
||||
|
||||
## Error Handling
|
||||
|
||||
The SDK provides comprehensive error handling through the `ComfyUIError` enum:
|
||||
|
||||
```rust
|
||||
use uni_comfyui_sdk::{UniComfyUIClient, ComfyUIError};
|
||||
|
||||
match client.get_metrics().await {
|
||||
Ok(metrics) => println!("Success: {:#?}", metrics),
|
||||
Err(ComfyUIError::Http(e)) => println!("HTTP error: {}", e),
|
||||
Err(ComfyUIError::Api { status, message }) => {
|
||||
println!("API error {}: {}", status, message)
|
||||
}
|
||||
Err(e) => println!("Other error: {}", e),
|
||||
}
|
||||
```
|
||||
|
||||
## Data Models
|
||||
|
||||
The SDK includes strongly typed models for all API requests and responses:
|
||||
|
||||
- `ServerRegistrationRequest` - Server registration data
|
||||
- `ServerHeartbeatRequest` - Server heartbeat data
|
||||
- `ServerStatusResponse` - Server status information
|
||||
- `ServerFiles` - Server file listings
|
||||
- `FileDetails` - Individual file information
|
||||
- And more...
|
||||
|
||||
## Examples
|
||||
|
||||
See the `examples/` directory for more usage examples.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
172
cargos/uni-comfyui-sdk/examples/advanced_usage.rs
Normal file
172
cargos/uni-comfyui-sdk/examples/advanced_usage.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
use uni_comfyui_sdk::{
|
||||
UniComfyUIClient, ComfyUIError, Result, ServerHeartbeatRequest, ServerRegistrationRequest,
|
||||
ServerUnregisterRequest,
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let client = UniComfyUIClient::new("http://192.168.0.148:18000")?;
|
||||
|
||||
// Demonstrate comprehensive server management
|
||||
println!("=== Server Management Demo ===");
|
||||
|
||||
// Register a new server
|
||||
let server_name = "demo-server";
|
||||
let registration = ServerRegistrationRequest {
|
||||
name: server_name.to_string(),
|
||||
http_url: "http://192.168.0.148:8188".to_string(),
|
||||
ws_url: "ws://192.168.0.148:8188".to_string(),
|
||||
};
|
||||
|
||||
match client.register_server(registration).await {
|
||||
Ok(response) => println!("✓ Server registered: {:?}", response),
|
||||
Err(ComfyUIError::Api { status: 409, .. }) => {
|
||||
println!("ℹ Server already exists (409 conflict)")
|
||||
}
|
||||
Err(e) => println!("✗ Failed to register server: {}", e),
|
||||
}
|
||||
|
||||
// Send heartbeat
|
||||
let heartbeat = ServerHeartbeatRequest {
|
||||
name: server_name.to_string(),
|
||||
};
|
||||
|
||||
match client.update_heartbeat(heartbeat).await {
|
||||
Ok(response) => println!("✓ Heartbeat sent: {:?}", response),
|
||||
Err(e) => println!("✗ Failed to send heartbeat: {}", e),
|
||||
}
|
||||
|
||||
// List all servers
|
||||
match client.list_all_servers().await {
|
||||
Ok(servers) => {
|
||||
println!("✓ Found {} servers:", servers.len());
|
||||
for server in servers {
|
||||
println!(" - {}: {} ({})", server.name, server.http_url, server.status);
|
||||
}
|
||||
}
|
||||
Err(e) => println!("✗ Failed to list servers: {}", e),
|
||||
}
|
||||
|
||||
// Demonstrate workflow management
|
||||
println!("\n=== Workflow Management Demo ===");
|
||||
|
||||
// Get all workflows
|
||||
match client.get_all_workflows().await {
|
||||
Ok(workflows) => println!("✓ Found {} workflows", workflows.len()),
|
||||
Err(e) => println!("✗ Failed to get workflows: {}", e),
|
||||
}
|
||||
|
||||
// Publish a sample workflow
|
||||
let mut sample_workflow = HashMap::new();
|
||||
sample_workflow.insert("name".to_string(), serde_json::json!("demo_workflow"));
|
||||
sample_workflow.insert("version".to_string(), serde_json::json!("1.0.0"));
|
||||
sample_workflow.insert(
|
||||
"description".to_string(),
|
||||
serde_json::json!("A demo workflow for testing"),
|
||||
);
|
||||
|
||||
match client.publish_workflow(sample_workflow).await {
|
||||
Ok(_) => println!("✓ Workflow published successfully"),
|
||||
Err(e) => println!("✗ Failed to publish workflow: {}", e),
|
||||
}
|
||||
|
||||
// Demonstrate workflow execution
|
||||
println!("\n=== Workflow Execution Demo ===");
|
||||
|
||||
let mut execution_data = HashMap::new();
|
||||
execution_data.insert("input_text".to_string(), serde_json::json!("Hello, ComfyUI!"));
|
||||
execution_data.insert("steps".to_string(), serde_json::json!(20));
|
||||
execution_data.insert("seed".to_string(), serde_json::json!(42));
|
||||
|
||||
match client
|
||||
.run_workflow("demo_workflow", Some("1.0.0"), execution_data)
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
println!("✓ Workflow execution started");
|
||||
|
||||
// Try to extract run ID from response for status checking
|
||||
if let Some(run_id) = response.get("run_id").and_then(|v| v.as_str()) {
|
||||
println!(" Run ID: {}", run_id);
|
||||
|
||||
// Poll for status (demo - in real usage you'd want better polling logic)
|
||||
for i in 1..=3 {
|
||||
sleep(Duration::from_secs(2)).await;
|
||||
match client.get_run_status(run_id).await {
|
||||
Ok(status) => {
|
||||
println!(" Status check {}: {:?}", i, status);
|
||||
}
|
||||
Err(e) => println!(" Status check {} failed: {}", i, e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => println!("✗ Failed to start workflow: {}", e),
|
||||
}
|
||||
|
||||
// Demonstrate monitoring
|
||||
println!("\n=== Monitoring Demo ===");
|
||||
|
||||
// System health
|
||||
match client.get_system_health().await {
|
||||
Ok(health) => println!("✓ System health: {:?}", health),
|
||||
Err(e) => println!("✗ Failed to get system health: {}", e),
|
||||
}
|
||||
|
||||
// System stats
|
||||
match client.get_system_stats().await {
|
||||
Ok(stats) => println!("✓ System stats retrieved"),
|
||||
Err(e) => println!("✗ Failed to get system stats: {}", e),
|
||||
}
|
||||
|
||||
// Recent tasks
|
||||
match client.get_recent_tasks(Some(5)).await {
|
||||
Ok(tasks) => println!("✓ Found {} recent tasks", tasks.len()),
|
||||
Err(e) => println!("✗ Failed to get recent tasks: {}", e),
|
||||
}
|
||||
|
||||
// Force health check
|
||||
match client.force_health_check().await {
|
||||
Ok(response) => println!("✓ Health check forced: {:?}", response),
|
||||
Err(e) => println!("✗ Failed to force health check: {}", e),
|
||||
}
|
||||
|
||||
// Demonstrate RunX endpoints
|
||||
println!("\n=== RunX Demo ===");
|
||||
|
||||
// Get schema for model_with_multi_dress
|
||||
match client.model_with_multi_dress_json_schema().await {
|
||||
Ok(schema) => println!("✓ Got model_with_multi_dress schema"),
|
||||
Err(e) => println!("✗ Failed to get schema: {}", e),
|
||||
}
|
||||
|
||||
// Try to run model_with_multi_dress workflow
|
||||
let mut dress_data = HashMap::new();
|
||||
dress_data.insert("model_image".to_string(), serde_json::json!("base64_image_data"));
|
||||
dress_data.insert("dress_images".to_string(), serde_json::json!(["dress1.jpg", "dress2.jpg"]));
|
||||
|
||||
match client
|
||||
.model_with_multi_dress(None, dress_data)
|
||||
.await
|
||||
{
|
||||
Ok(response) => println!("✓ Model with multi dress executed: {:?}", response),
|
||||
Err(e) => println!("✗ Failed to execute model with multi dress: {}", e),
|
||||
}
|
||||
|
||||
// Cleanup: Unregister the demo server
|
||||
println!("\n=== Cleanup ===");
|
||||
let unregister = ServerUnregisterRequest {
|
||||
name: server_name.to_string(),
|
||||
};
|
||||
|
||||
match client.unregister_server(unregister).await {
|
||||
Ok(response) => println!("✓ Server unregistered: {:?}", response),
|
||||
Err(e) => println!("✗ Failed to unregister server: {}", e),
|
||||
}
|
||||
|
||||
println!("\n=== Demo Complete ===");
|
||||
Ok(())
|
||||
}
|
||||
56
cargos/uni-comfyui-sdk/examples/basic_usage.rs
Normal file
56
cargos/uni-comfyui-sdk/examples/basic_usage.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use std::collections::HashMap;
|
||||
use uni_comfyui_sdk::{UniComfyUIClient, Result, ServerRegistrationRequest};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// Create a new client
|
||||
let client = UniComfyUIClient::new("http://192.168.0.148:18000")?;
|
||||
|
||||
// Get system metrics
|
||||
println!("Getting system metrics...");
|
||||
let metrics = client.get_metrics().await?;
|
||||
println!("Metrics: {:#?}", metrics);
|
||||
|
||||
// Get all workflows
|
||||
println!("\nGetting all workflows...");
|
||||
let workflows = client.get_all_workflows().await?;
|
||||
println!("Found {} workflows", workflows.len());
|
||||
|
||||
// Get servers status
|
||||
println!("\nGetting servers status...");
|
||||
let servers = client.get_servers_status().await?;
|
||||
println!("Found {} servers", servers.len());
|
||||
|
||||
// Health check
|
||||
println!("\nPerforming health check...");
|
||||
let health = client.health_check().await?;
|
||||
println!("Health status: {:#?}", health);
|
||||
|
||||
// Example: Register a new server
|
||||
println!("\nRegistering a new server...");
|
||||
let registration_request = ServerRegistrationRequest {
|
||||
name: "test-server".to_string(),
|
||||
http_url: "http://192.168.0.148:8188".to_string(),
|
||||
ws_url: "ws://192.168.0.148:8188".to_string(),
|
||||
};
|
||||
|
||||
match client.register_server(registration_request).await {
|
||||
Ok(response) => println!("Server registered: {:#?}", response),
|
||||
Err(e) => println!("Failed to register server: {}", e),
|
||||
}
|
||||
|
||||
// Example: Run a workflow
|
||||
println!("\nRunning a workflow...");
|
||||
let mut workflow_data = HashMap::new();
|
||||
workflow_data.insert("input".to_string(), serde_json::json!("test input"));
|
||||
|
||||
match client
|
||||
.run_workflow("test_workflow", None, workflow_data)
|
||||
.await
|
||||
{
|
||||
Ok(response) => println!("Workflow started: {:#?}", response),
|
||||
Err(e) => println!("Failed to start workflow: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
291
cargos/uni-comfyui-sdk/src/client.rs
Normal file
291
cargos/uni-comfyui-sdk/src/client.rs
Normal file
@@ -0,0 +1,291 @@
|
||||
use crate::error::{ComfyUIError, Result};
|
||||
use crate::models::*;
|
||||
use crate::types::*;
|
||||
use reqwest::{Client, Response};
|
||||
use serde_json::Value;
|
||||
use url::Url;
|
||||
|
||||
/// UniComfyUI API client
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UniComfyUIClient {
|
||||
client: Client,
|
||||
base_url: Url,
|
||||
}
|
||||
|
||||
impl UniComfyUIClient {
|
||||
/// Create a new UniComfyUI client
|
||||
pub fn new(base_url: &str) -> Result<Self> {
|
||||
let base_url = Url::parse(base_url)?;
|
||||
let client = Client::new();
|
||||
|
||||
Ok(Self { client, base_url })
|
||||
}
|
||||
|
||||
/// Create a new UniComfyUI client with custom reqwest client
|
||||
pub fn with_client(base_url: &str, client: Client) -> Result<Self> {
|
||||
let base_url = Url::parse(base_url)?;
|
||||
|
||||
Ok(Self { client, base_url })
|
||||
}
|
||||
|
||||
/// Build URL for an endpoint
|
||||
fn build_url(&self, path: &str) -> Result<Url> {
|
||||
Ok(self.base_url.join(path)?)
|
||||
}
|
||||
|
||||
/// Handle HTTP response and convert to appropriate result
|
||||
async fn handle_response<T>(&self, response: Response) -> Result<T>
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
{
|
||||
let status = response.status();
|
||||
|
||||
if status.is_success() {
|
||||
let text = response.text().await?;
|
||||
if text.is_empty() {
|
||||
// Handle empty responses for some endpoints
|
||||
return Ok(serde_json::from_value(Value::Null)?);
|
||||
}
|
||||
Ok(serde_json::from_str(&text)?)
|
||||
} else {
|
||||
let error_text = response.text().await.unwrap_or_default();
|
||||
Err(ComfyUIError::Api {
|
||||
status: status.as_u16(),
|
||||
message: error_text,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Status endpoints
|
||||
|
||||
/// Get metrics - 获取队列状态概览
|
||||
pub async fn get_metrics(&self) -> Result<GenericResponse> {
|
||||
let url = self.build_url("/api/service/metrics")?;
|
||||
let response = self.client.get(url).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// Get servers status - 获取所有已配置的ComfyUI服务器的配置信息和实时状态
|
||||
pub async fn get_servers_status(&self) -> Result<Vec<ServerStatus>> {
|
||||
let url = self.build_url("/api/service/servers_status")?;
|
||||
let response = self.client.get(url).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// List server files - 获取指定ComfyUI服务器的输入和输出文件夹中的文件列表
|
||||
pub async fn list_server_files(&self, server_index: u32) -> Result<ServerFiles> {
|
||||
let url = self.build_url(&format!("/api/service/servers/{}/files", server_index))?;
|
||||
let response = self.client.get(url).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
// Workflow endpoints
|
||||
|
||||
/// Get all workflows - 获取所有工作流
|
||||
pub async fn get_all_workflows(&self) -> Result<ObjectArrayResponse> {
|
||||
let url = self.build_url("/api/workflow")?;
|
||||
let response = self.client.get(url).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// Publish workflow - 发布工作流
|
||||
pub async fn publish_workflow(&self, data: JsonObject) -> Result<GenericResponse> {
|
||||
let url = self.build_url("/api/workflow")?;
|
||||
let response = self.client.post(url).json(&data).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// Delete workflow - 删除工作流
|
||||
pub async fn delete_workflow(&self, workflow_name: &str) -> Result<GenericResponse> {
|
||||
let url = self.build_url(&format!("/api/workflow/{}", workflow_name))?;
|
||||
let response = self.client.delete(url).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// Get one workflow - 获取工作流规范
|
||||
pub async fn get_one_workflow(
|
||||
&self,
|
||||
base_name: &str,
|
||||
version: Option<&str>,
|
||||
) -> Result<GenericResponse> {
|
||||
let mut url = self.build_url(&format!("/api/workflow/{}", base_name))?;
|
||||
|
||||
if let Some(version) = version {
|
||||
url.query_pairs_mut().append_pair("version", version);
|
||||
}
|
||||
|
||||
let response = self.client.get(url).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
// Run endpoints
|
||||
|
||||
/// Run workflow - 异步执行工作流
|
||||
pub async fn run_workflow(
|
||||
&self,
|
||||
workflow_name: &str,
|
||||
workflow_version: Option<&str>,
|
||||
data: JsonObject,
|
||||
) -> Result<GenericResponse> {
|
||||
let mut url = self.build_url("/api/run")?;
|
||||
url.query_pairs_mut().append_pair("workflow_name", workflow_name);
|
||||
|
||||
if let Some(version) = workflow_version {
|
||||
url.query_pairs_mut().append_pair("workflow_version", version);
|
||||
}
|
||||
|
||||
let response = self.client.post(url).json(&data).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// Get run status - 获取工作流执行状态
|
||||
pub async fn get_run_status(&self, workflow_run_id: &str) -> Result<GenericResponse> {
|
||||
let url = self.build_url(&format!("/api/run/{}", workflow_run_id))?;
|
||||
let response = self.client.get(url).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
// RunX endpoints
|
||||
|
||||
/// Model with multi dress - 一个模特,穿多件不同的衣服
|
||||
pub async fn model_with_multi_dress(
|
||||
&self,
|
||||
workflow_version: Option<&str>,
|
||||
data: JsonObject,
|
||||
) -> Result<GenericResponse> {
|
||||
let mut url = self.build_url("/api/runx/model_with_multi_dress")?;
|
||||
|
||||
if let Some(version) = workflow_version {
|
||||
url.query_pairs_mut().append_pair("workflow_version", version);
|
||||
}
|
||||
|
||||
let response = self.client.post(url).json(&data).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// Get model with multi dress JSON schema - 获取工作流定义
|
||||
pub async fn model_with_multi_dress_json_schema(&self) -> Result<GenericResponse> {
|
||||
let url = self.build_url("/api/runx/model_with_multi_dress/json_schema")?;
|
||||
let response = self.client.get(url).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
// Monitor endpoints
|
||||
|
||||
/// Monitor dashboard - 监控仪表板页面
|
||||
pub async fn monitor_dashboard(&self) -> Result<String> {
|
||||
let url = self.build_url("/monitor/")?;
|
||||
let response = self.client.get(url).send().await?;
|
||||
|
||||
let status = response.status();
|
||||
if status.is_success() {
|
||||
Ok(response.text().await?)
|
||||
} else {
|
||||
let error_text = response.text().await.unwrap_or_default();
|
||||
Err(ComfyUIError::Api {
|
||||
status: status.as_u16(),
|
||||
message: error_text,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Get system stats - 获取系统统计信息
|
||||
pub async fn get_system_stats(&self) -> Result<AdditionalPropertiesResponse> {
|
||||
let url = self.build_url("/monitor/system-stats")?;
|
||||
let response = self.client.get(url).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// Get task stats - 获取任务统计信息
|
||||
pub async fn get_task_stats(&self) -> Result<AdditionalPropertiesResponse> {
|
||||
let url = self.build_url("/monitor/task-stats")?;
|
||||
let response = self.client.get(url).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// Get server status (monitor) - 获取服务器状态信息
|
||||
pub async fn get_monitor_server_status(&self) -> Result<ObjectArrayResponse> {
|
||||
let url = self.build_url("/monitor/server-status")?;
|
||||
let response = self.client.get(url).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// Get recent tasks - 获取最近的任务列表
|
||||
pub async fn get_recent_tasks(&self, limit: Option<i32>) -> Result<ObjectArrayResponse> {
|
||||
let mut url = self.build_url("/monitor/recent-tasks")?;
|
||||
|
||||
if let Some(limit) = limit {
|
||||
url.query_pairs_mut().append_pair("limit", &limit.to_string());
|
||||
}
|
||||
|
||||
let response = self.client.get(url).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// Health check - 健康检查端点
|
||||
pub async fn health_check(&self) -> Result<AdditionalPropertiesResponse> {
|
||||
let url = self.build_url("/monitor/health")?;
|
||||
let response = self.client.get(url).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
// ComfyUI Server Management endpoints
|
||||
|
||||
/// Register server - 注册ComfyUI服务器
|
||||
pub async fn register_server(
|
||||
&self,
|
||||
request: ServerRegistrationRequest,
|
||||
) -> Result<StringDictResponse> {
|
||||
let url = self.build_url("/api/comfy/register")?;
|
||||
let response = self.client.post(url).json(&request).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// Update heartbeat - 更新服务器心跳
|
||||
pub async fn update_heartbeat(
|
||||
&self,
|
||||
request: ServerHeartbeatRequest,
|
||||
) -> Result<StringDictResponse> {
|
||||
let url = self.build_url("/api/comfy/heartbeat")?;
|
||||
let response = self.client.post(url).json(&request).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// Unregister server - 注销ComfyUI服务器
|
||||
pub async fn unregister_server(
|
||||
&self,
|
||||
request: ServerUnregisterRequest,
|
||||
) -> Result<StringDictResponse> {
|
||||
let url = self.build_url("/api/comfy/unregister")?;
|
||||
let response = self.client.post(url).json(&request).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// Get server status - 获取指定服务器状态
|
||||
pub async fn get_server_status(&self, server_name: &str) -> Result<ServerStatusResponse> {
|
||||
let url = self.build_url(&format!("/api/comfy/status/{}", server_name))?;
|
||||
let response = self.client.get(url).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// List all servers - 获取所有服务器列表
|
||||
pub async fn list_all_servers(&self) -> Result<Vec<ServerStatusResponse>> {
|
||||
let url = self.build_url("/api/comfy/list")?;
|
||||
let response = self.client.get(url).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// Get system health - 获取系统整体健康状态
|
||||
pub async fn get_system_health(&self) -> Result<AdditionalPropertiesResponse> {
|
||||
let url = self.build_url("/api/comfy/health")?;
|
||||
let response = self.client.get(url).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// Force health check - 强制执行健康检查
|
||||
pub async fn force_health_check(&self) -> Result<StringDictResponse> {
|
||||
let url = self.build_url("/api/comfy/force_health_check")?;
|
||||
let response = self.client.post(url).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
}
|
||||
40
cargos/uni-comfyui-sdk/src/error.rs
Normal file
40
cargos/uni-comfyui-sdk/src/error.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use thiserror::Error;
|
||||
|
||||
/// Result type alias for ComfyUI SDK operations
|
||||
pub type Result<T> = std::result::Result<T, ComfyUIError>;
|
||||
|
||||
/// Error types for ComfyUI SDK operations
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ComfyUIError {
|
||||
/// HTTP request error
|
||||
#[error("HTTP request failed: {0}")]
|
||||
Http(#[from] reqwest::Error),
|
||||
|
||||
/// JSON serialization/deserialization error
|
||||
#[error("JSON error: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
/// URL parsing error
|
||||
#[error("URL parsing error: {0}")]
|
||||
Url(#[from] url::ParseError),
|
||||
|
||||
/// API error response
|
||||
#[error("API error: {status} - {message}")]
|
||||
Api { status: u16, message: String },
|
||||
|
||||
/// Validation error
|
||||
#[error("Validation error: {0}")]
|
||||
Validation(String),
|
||||
|
||||
/// Server not found error
|
||||
#[error("Server not found: {0}")]
|
||||
ServerNotFound(String),
|
||||
|
||||
/// Workflow not found error
|
||||
#[error("Workflow not found: {0}")]
|
||||
WorkflowNotFound(String),
|
||||
|
||||
/// Generic error
|
||||
#[error("Error: {0}")]
|
||||
Generic(String),
|
||||
}
|
||||
58
cargos/uni-comfyui-sdk/src/lib.rs
Normal file
58
cargos/uni-comfyui-sdk/src/lib.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
//! # uni-comfyui-sdk
|
||||
//!
|
||||
//! Rust SDK for ComfyUI Workflow Service & Management API
|
||||
//!
|
||||
//! This SDK provides a type-safe interface to interact with the ComfyUI API,
|
||||
//! including workflow management, execution, monitoring, and server management.
|
||||
//!
|
||||
//! ## Features
|
||||
//!
|
||||
//! - **Status Management**: Get metrics, server status, and file listings
|
||||
//! - **Workflow Management**: Create, retrieve, and delete workflows
|
||||
//! - **Workflow Execution**: Run workflows and monitor their status
|
||||
//! - **Server Management**: Register, unregister, and manage ComfyUI servers
|
||||
//! - **Monitoring**: Health checks, system stats, and task monitoring
|
||||
//! - **Type Safety**: Strongly typed API with comprehensive error handling
|
||||
//!
|
||||
//! ## Quick Start
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use uni_comfyui_sdk::{UniComfyUIClient, Result};
|
||||
//! use std::collections::HashMap;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() -> Result<()> {
|
||||
//! // Create a client
|
||||
//! let client = UniComfyUIClient::new("http://localhost:8000")?;
|
||||
//!
|
||||
//! // Get system metrics
|
||||
//! let metrics = client.get_metrics().await?;
|
||||
//! println!("Metrics: {:#?}", metrics);
|
||||
//!
|
||||
//! // Run a workflow
|
||||
//! let mut data = HashMap::new();
|
||||
//! data.insert("input".to_string(), serde_json::json!("test"));
|
||||
//! let result = client.run_workflow("my_workflow", None, data).await?;
|
||||
//! println!("Workflow result: {:#?}", result);
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
pub mod client;
|
||||
pub mod error;
|
||||
pub mod models;
|
||||
pub mod types;
|
||||
|
||||
pub use client::UniComfyUIClient;
|
||||
pub use error::{ComfyUIError, Result};
|
||||
pub use models::*;
|
||||
pub use types::*;
|
||||
|
||||
/// Re-export commonly used types
|
||||
pub mod prelude {
|
||||
pub use crate::client::UniComfyUIClient;
|
||||
pub use crate::error::{ComfyUIError, Result};
|
||||
pub use crate::models::*;
|
||||
pub use crate::types::*;
|
||||
}
|
||||
130
cargos/uni-comfyui-sdk/src/models.rs
Normal file
130
cargos/uni-comfyui-sdk/src/models.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// File details information
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FileDetails {
|
||||
/// File name
|
||||
pub name: String,
|
||||
/// File size in KB
|
||||
pub size_kb: f64,
|
||||
/// Last modified timestamp
|
||||
pub modified_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// HTTP validation error details
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HTTPValidationError {
|
||||
/// List of validation errors
|
||||
pub detail: Vec<ValidationError>,
|
||||
}
|
||||
|
||||
/// Individual validation error
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ValidationError {
|
||||
/// Error location path
|
||||
pub loc: Vec<ValidationErrorLocation>,
|
||||
/// Error message
|
||||
pub msg: String,
|
||||
/// Error type
|
||||
#[serde(rename = "type")]
|
||||
pub error_type: String,
|
||||
}
|
||||
|
||||
/// Validation error location (can be string or integer)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ValidationErrorLocation {
|
||||
String(String),
|
||||
Integer(i32),
|
||||
}
|
||||
|
||||
/// Server files information
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerFiles {
|
||||
/// Server index in configuration list
|
||||
pub server_index: i32,
|
||||
/// HTTP URL of the server
|
||||
pub http_url: String,
|
||||
/// List of input files
|
||||
pub input_files: Vec<FileDetails>,
|
||||
/// List of output files
|
||||
pub output_files: Vec<FileDetails>,
|
||||
}
|
||||
|
||||
/// Server heartbeat request
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerHeartbeatRequest {
|
||||
/// Server name
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Server queue details
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerQueueDetails {
|
||||
/// Number of running tasks
|
||||
pub running_count: i32,
|
||||
/// Number of pending tasks
|
||||
pub pending_count: i32,
|
||||
}
|
||||
|
||||
/// Server registration request
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerRegistrationRequest {
|
||||
/// Server name
|
||||
pub name: String,
|
||||
/// HTTP URL of the server
|
||||
pub http_url: String,
|
||||
/// WebSocket URL of the server
|
||||
pub ws_url: String,
|
||||
}
|
||||
|
||||
/// Server status information
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerStatus {
|
||||
/// Server index in configuration list
|
||||
pub server_index: i32,
|
||||
/// HTTP URL of the server
|
||||
pub http_url: String,
|
||||
/// WebSocket URL of the server
|
||||
pub ws_url: String,
|
||||
/// Whether the server is reachable
|
||||
pub is_reachable: bool,
|
||||
/// Whether the server is free
|
||||
pub is_free: bool,
|
||||
/// Queue details
|
||||
pub queue_details: ServerQueueDetails,
|
||||
}
|
||||
|
||||
/// Server status response
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerStatusResponse {
|
||||
/// Server name
|
||||
pub name: String,
|
||||
/// HTTP URL of the server
|
||||
pub http_url: String,
|
||||
/// WebSocket URL of the server
|
||||
pub ws_url: String,
|
||||
/// Server status
|
||||
pub status: String,
|
||||
/// Last heartbeat timestamp
|
||||
pub last_heartbeat: Option<String>,
|
||||
/// Last health check timestamp
|
||||
pub last_health_check: Option<String>,
|
||||
/// Current number of tasks
|
||||
pub current_tasks: i32,
|
||||
/// Maximum concurrent tasks
|
||||
pub max_concurrent_tasks: i32,
|
||||
/// Server capabilities
|
||||
pub capabilities: HashMap<String, serde_json::Value>,
|
||||
/// Server metadata
|
||||
pub metadata: HashMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
/// Server unregister request
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerUnregisterRequest {
|
||||
/// Server name
|
||||
pub name: String,
|
||||
}
|
||||
16
cargos/uni-comfyui-sdk/src/types.rs
Normal file
16
cargos/uni-comfyui-sdk/src/types.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Generic JSON object type
|
||||
pub type JsonObject = HashMap<String, serde_json::Value>;
|
||||
|
||||
/// Generic response type for endpoints that return arbitrary JSON
|
||||
pub type GenericResponse = serde_json::Value;
|
||||
|
||||
/// Response type for endpoints that return a dictionary of strings
|
||||
pub type StringDictResponse = HashMap<String, String>;
|
||||
|
||||
/// Response type for endpoints that return an array of objects
|
||||
pub type ObjectArrayResponse = Vec<JsonObject>;
|
||||
|
||||
/// Response type for endpoints that return an object with additional properties
|
||||
pub type AdditionalPropertiesResponse = JsonObject;
|
||||
90
cargos/uni-comfyui-sdk/tests/integration_tests.rs
Normal file
90
cargos/uni-comfyui-sdk/tests/integration_tests.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use std::collections::HashMap;
|
||||
use uni_comfyui_sdk::{UniComfyUIClient, Result, ServerRegistrationRequest};
|
||||
|
||||
// Note: These tests require a running ComfyUI service
|
||||
// They are disabled by default and should be run manually when needed
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_client_creation() -> Result<()> {
|
||||
let _client = UniComfyUIClient::new("http://192.168.0.148:18000")?;
|
||||
assert!(true); // Just test that client creation doesn't panic
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_get_metrics() -> Result<()> {
|
||||
let client = UniComfyUIClient::new("http://192.168.0.148:18000")?;
|
||||
let _metrics = client.get_metrics().await?;
|
||||
// Just test that the call doesn't fail
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_get_all_workflows() -> Result<()> {
|
||||
let client = UniComfyUIClient::new("http://192.168.0.148:18000")?;
|
||||
let workflows = client.get_all_workflows().await?;
|
||||
assert!(workflows.is_empty() || !workflows.is_empty()); // Just test the call
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_health_check() -> Result<()> {
|
||||
let client = UniComfyUIClient::new("http://192.168.0.148:18000")?;
|
||||
let _health = client.health_check().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_server_registration() -> Result<()> {
|
||||
let client = UniComfyUIClient::new("http://192.168.0.148:18000")?;
|
||||
|
||||
let registration_request = ServerRegistrationRequest {
|
||||
name: "test-server".to_string(),
|
||||
http_url: "http://192.168.0.148:8188".to_string(),
|
||||
ws_url: "ws://192.168.0.148:8188".to_string(),
|
||||
};
|
||||
|
||||
// This might fail if server already exists, which is fine for testing
|
||||
let _result = client.register_server(registration_request).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_workflow_execution() -> Result<()> {
|
||||
let client = UniComfyUIClient::new("http://192.168.0.148:18000")?;
|
||||
|
||||
let mut workflow_data = HashMap::new();
|
||||
workflow_data.insert("test_input".to_string(), serde_json::json!("test_value"));
|
||||
|
||||
// This will likely fail without a real workflow, but tests the API call
|
||||
let _result = client
|
||||
.run_workflow("test_workflow", None, workflow_data)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_url_building() {
|
||||
let _client = UniComfyUIClient::new("http://192.168.0.148:18000").unwrap();
|
||||
// Test that URL building works correctly
|
||||
assert!(true); // Basic test that client creation works
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_types() {
|
||||
use uni_comfyui_sdk::ComfyUIError;
|
||||
|
||||
// Test that error types can be created
|
||||
let _error = ComfyUIError::Validation("test error".to_string());
|
||||
let _error = ComfyUIError::ServerNotFound("test server".to_string());
|
||||
let _error = ComfyUIError::WorkflowNotFound("test workflow".to_string());
|
||||
|
||||
assert!(true);
|
||||
}
|
||||
1173
cargos/uni-comfyui-sdk/unicomfy.json
Normal file
1173
cargos/uni-comfyui-sdk/unicomfy.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user