feat: add comfyui sdk
This commit is contained in:
271
cargos/comfyui-sdk/examples/main.rs
Normal file
271
cargos/comfyui-sdk/examples/main.rs
Normal file
@@ -0,0 +1,271 @@
|
||||
//! ComfyUI SDK Examples
|
||||
//!
|
||||
//! This example demonstrates how to use the ComfyUI SDK for Rust
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use comfyui_sdk::{
|
||||
ComfyUIClient, ComfyUIClientConfig, ExecutionOptions,
|
||||
AI_MODEL_FACE_HAIR_FIX_TEMPLATE
|
||||
};
|
||||
use comfyui_sdk::utils::SimpleCallbacks;
|
||||
use serde_json::json;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialize logging (optional)
|
||||
// env_logger::init();
|
||||
|
||||
println!("🚀 Starting ComfyUI SDK Rust Examples");
|
||||
|
||||
// Run the AI model face hair fix example
|
||||
ai_model_face_hair_fix_example().await?;
|
||||
|
||||
// Run template validation example
|
||||
template_validation_example().await?;
|
||||
|
||||
println!("✅ All examples completed successfully!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// AI Model Face & Hair Detail Fix Example
|
||||
async fn ai_model_face_hair_fix_example() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("\n🎨 AI Model Face & Hair Detail Fix Example");
|
||||
println!("===========================================");
|
||||
|
||||
// Initialize client
|
||||
let mut client = ComfyUIClient::new(ComfyUIClientConfig {
|
||||
base_url: "http://192.168.0.193:8188".to_string(),
|
||||
..Default::default()
|
||||
})?;
|
||||
|
||||
// Connect to ComfyUI server
|
||||
println!("📡 Connecting to ComfyUI server...");
|
||||
client.connect().await?;
|
||||
println!("✅ Connected successfully!");
|
||||
|
||||
// Register the built-in template
|
||||
println!("📝 Registering AI Model Face & Hair Fix template...");
|
||||
client.templates().register_from_data(AI_MODEL_FACE_HAIR_FIX_TEMPLATE.clone())?;
|
||||
println!("✅ Template registered!");
|
||||
|
||||
// Get the registered template
|
||||
let template = client.templates_ref()
|
||||
.get_by_id(&AI_MODEL_FACE_HAIR_FIX_TEMPLATE.metadata.id)
|
||||
.ok_or("Failed to get template")?;
|
||||
|
||||
println!("📋 Template ID: {}", template.id());
|
||||
println!("📋 Template Name: {}", template.name());
|
||||
if let Some(description) = template.description() {
|
||||
println!("📋 Template Description: {}", description);
|
||||
}
|
||||
|
||||
// Display template parameters
|
||||
println!("\n🔍 Template Parameters:");
|
||||
for (name, schema) in template.parameters() {
|
||||
println!(" - {}: {:?} ({})",
|
||||
name,
|
||||
schema.param_type,
|
||||
schema.description.as_deref().unwrap_or("No description")
|
||||
);
|
||||
}
|
||||
|
||||
// Create execution callbacks
|
||||
let callbacks = Arc::new(
|
||||
SimpleCallbacks::new()
|
||||
.with_progress(|progress| {
|
||||
let percentage = (progress.progress as f64 / progress.max as f64 * 100.0) as u32;
|
||||
println!("⏳ Progress: {}% ({}/{}) - Node: {}",
|
||||
percentage, progress.progress, progress.max, progress.node_id);
|
||||
})
|
||||
.with_executing(|node_id| {
|
||||
println!("🔄 Executing node: {}", node_id);
|
||||
})
|
||||
.with_executed(|result| {
|
||||
println!("✅ Node executed - Prompt ID: {}", result.prompt_id);
|
||||
})
|
||||
.with_error(|error| {
|
||||
println!("❌ Execution error: {}", error.message);
|
||||
})
|
||||
);
|
||||
|
||||
// Execute the template with test parameters
|
||||
println!("\n🎨 Executing AI Model Face & Hair Enhancement...");
|
||||
let mut parameters = HashMap::new();
|
||||
parameters.insert("input_image".to_string(), json!("20250806-190606.jpg"));
|
||||
parameters.insert("face_prompt".to_string(), json!("beautiful woman, perfect skin, detailed facial features"));
|
||||
parameters.insert("face_denoise".to_string(), json!("0.25"));
|
||||
|
||||
let execution_options = ExecutionOptions {
|
||||
timeout: Some(std::time::Duration::from_secs(120)), // 2 minutes timeout
|
||||
priority: None,
|
||||
};
|
||||
|
||||
let result = client.execute_template_with_callbacks(
|
||||
template,
|
||||
parameters,
|
||||
execution_options,
|
||||
callbacks,
|
||||
).await?;
|
||||
|
||||
if result.success {
|
||||
println!("\n🎉 AI Model Enhancement completed successfully!");
|
||||
println!("📊 Execution time: {}ms", result.execution_time);
|
||||
println!("🆔 Prompt ID: {}", result.prompt_id);
|
||||
|
||||
if let Some(outputs) = &result.outputs {
|
||||
println!("📁 Outputs: {}", serde_json::to_string_pretty(outputs)?);
|
||||
|
||||
// Convert outputs to HTTP URLs
|
||||
let image_urls = client.outputs_to_urls(outputs);
|
||||
if !image_urls.is_empty() {
|
||||
println!("🖼️ Enhanced Image URLs:");
|
||||
for url in image_urls {
|
||||
println!(" - {}", url);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("❌ AI Model Enhancement failed");
|
||||
if let Some(error) = &result.error {
|
||||
println!(" Error: {}", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnect
|
||||
println!("\n🔌 Disconnecting...");
|
||||
client.disconnect().await?;
|
||||
println!("👋 Disconnected!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Template validation example
|
||||
async fn template_validation_example() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("\n🔍 Template Validation Example");
|
||||
println!("==============================");
|
||||
|
||||
let mut client = ComfyUIClient::new(ComfyUIClientConfig {
|
||||
base_url: "http://192.168.0.193:8188".to_string(),
|
||||
..Default::default()
|
||||
})?;
|
||||
|
||||
// Register template
|
||||
client.templates().register_from_data(AI_MODEL_FACE_HAIR_FIX_TEMPLATE.clone())?;
|
||||
let template = client.templates_ref()
|
||||
.get_by_id(&AI_MODEL_FACE_HAIR_FIX_TEMPLATE.metadata.id)
|
||||
.ok_or("Failed to get template")?;
|
||||
|
||||
println!("📝 Testing parameter validation...");
|
||||
|
||||
// Test valid parameters
|
||||
let mut valid_params = HashMap::new();
|
||||
valid_params.insert("input_image".to_string(), json!("test.jpg"));
|
||||
valid_params.insert("face_prompt".to_string(), json!("beautiful face"));
|
||||
valid_params.insert("face_denoise".to_string(), json!("0.3"));
|
||||
|
||||
let validation_result = template.validate(&valid_params);
|
||||
println!("✅ Valid parameters test: {}",
|
||||
if validation_result.valid { "PASSED" } else { "FAILED" });
|
||||
|
||||
if !validation_result.valid {
|
||||
println!("❌ Validation errors:");
|
||||
for error in &validation_result.errors {
|
||||
println!(" - {}: {}", error.path, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Test invalid parameters
|
||||
let mut invalid_params = HashMap::new();
|
||||
invalid_params.insert("input_image".to_string(), json!("")); // Empty required field
|
||||
invalid_params.insert("face_prompt".to_string(), json!("test"));
|
||||
invalid_params.insert("unknown_param".to_string(), json!("value")); // Unknown parameter
|
||||
|
||||
let invalid_validation = template.validate(&invalid_params);
|
||||
println!("🚫 Invalid parameters test: {}",
|
||||
if !invalid_validation.valid { "PASSED" } else { "FAILED" });
|
||||
|
||||
if !invalid_validation.valid {
|
||||
println!("📋 Expected validation errors:");
|
||||
for error in &invalid_validation.errors {
|
||||
println!(" - {}: {}", error.path, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Test template instance creation
|
||||
println!("\n🏗️ Testing template instance creation...");
|
||||
match template.create_instance(valid_params) {
|
||||
Ok(instance) => {
|
||||
println!("✅ Template instance created successfully");
|
||||
println!(" Template ID: {}", instance.template_id());
|
||||
println!(" Template Name: {}", instance.template_name());
|
||||
println!(" Node count: {}", instance.node_count());
|
||||
}
|
||||
Err(e) => {
|
||||
println!("❌ Failed to create template instance: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Test template manager features
|
||||
println!("\n📚 Testing template manager features...");
|
||||
let templates = client.templates_ref();
|
||||
println!(" Total templates: {}", templates.count());
|
||||
println!(" Template IDs: {:?}", templates.list_ids());
|
||||
|
||||
let categories = templates.get_categories();
|
||||
if !categories.is_empty() {
|
||||
println!(" Categories: {:?}", categories);
|
||||
}
|
||||
|
||||
let tags = templates.get_tags();
|
||||
if !tags.is_empty() {
|
||||
println!(" Tags: {:?}", tags);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Basic usage example (simpler version)
|
||||
#[allow(dead_code)]
|
||||
async fn basic_usage_example() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("\n📖 Basic Usage Example");
|
||||
println!("======================");
|
||||
|
||||
// Create client
|
||||
let mut client = ComfyUIClient::new(ComfyUIClientConfig {
|
||||
base_url: "http://localhost:8188".to_string(),
|
||||
..Default::default()
|
||||
})?;
|
||||
|
||||
// Connect
|
||||
client.connect().await?;
|
||||
println!("✅ Connected to ComfyUI!");
|
||||
|
||||
// Get system stats
|
||||
match client.get_system_stats().await {
|
||||
Ok(stats) => {
|
||||
println!("💻 System Info:");
|
||||
println!(" OS: {}", stats.system.os);
|
||||
println!(" Python: {}", stats.system.python_version);
|
||||
println!(" Devices: {}", stats.devices.len());
|
||||
}
|
||||
Err(e) => {
|
||||
println!("❌ Failed to get system stats: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Get queue status
|
||||
match client.get_queue_status().await {
|
||||
Ok(queue) => {
|
||||
println!("📋 Queue Status:");
|
||||
println!(" Running: {}", queue.queue_running.len());
|
||||
println!(" Pending: {}", queue.queue_pending.len());
|
||||
}
|
||||
Err(e) => {
|
||||
println!("❌ Failed to get queue status: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
client.disconnect().await?;
|
||||
Ok(())
|
||||
}
|
||||
306
cargos/comfyui-sdk/examples/real_local_image_test.rs
Normal file
306
cargos/comfyui-sdk/examples/real_local_image_test.rs
Normal file
@@ -0,0 +1,306 @@
|
||||
//! Real Local Image Upload Test
|
||||
//!
|
||||
//! This example demonstrates how to upload your actual local image file
|
||||
//! and use it with the AI Model Face & Hair Detail Fix template.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use comfyui_sdk::{
|
||||
ComfyUIClient, ComfyUIClientConfig, ExecutionOptions,
|
||||
AI_MODEL_FACE_HAIR_FIX_TEMPLATE
|
||||
};
|
||||
use comfyui_sdk::utils::SimpleCallbacks;
|
||||
use serde_json::json;
|
||||
|
||||
/// Test with real local image upload
|
||||
async fn test_with_real_local_image() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("🚀 Starting Real Local Image Upload Test");
|
||||
|
||||
// Initialize client
|
||||
let mut client = ComfyUIClient::new(ComfyUIClientConfig {
|
||||
base_url: "http://192.168.0.193:8188".to_string(),
|
||||
..Default::default()
|
||||
})?;
|
||||
|
||||
// Connect to ComfyUI server
|
||||
println!("📡 Connecting to ComfyUI server...");
|
||||
client.connect().await?;
|
||||
println!("✅ Connected successfully!");
|
||||
|
||||
// Register the template
|
||||
println!("📝 Registering AI Model Face & Hair Fix template...");
|
||||
client.templates().register_from_data(AI_MODEL_FACE_HAIR_FIX_TEMPLATE.clone())?;
|
||||
let template = client.templates_ref()
|
||||
.get_by_id(&AI_MODEL_FACE_HAIR_FIX_TEMPLATE.metadata.id)
|
||||
.ok_or("Failed to register template")?;
|
||||
println!("✅ Template registered!");
|
||||
|
||||
// Your actual local image path
|
||||
let local_image_path = "20250808-111737.png";
|
||||
|
||||
println!("\n📤 Uploading your image: {}", local_image_path);
|
||||
println!("⏳ This may take a moment depending on file size and network speed...");
|
||||
|
||||
match upload_and_execute(&client, template, local_image_path).await {
|
||||
Ok(_) => println!("✅ Upload and execution completed successfully!"),
|
||||
Err(e) => {
|
||||
println!("\n💥 Upload/Execution failed: {}", e);
|
||||
show_troubleshooting_tips(&e);
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnect
|
||||
println!("\n🔌 Disconnecting from ComfyUI server...");
|
||||
client.disconnect().await?;
|
||||
println!("👋 Disconnected successfully!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Upload image and execute template
|
||||
async fn upload_and_execute(
|
||||
client: &ComfyUIClient,
|
||||
template: &comfyui_sdk::WorkflowTemplate,
|
||||
image_path: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Step 1: Upload image to ComfyUI server
|
||||
println!("\n🔄 Step 1: Uploading image to ComfyUI server...");
|
||||
|
||||
if !Path::new(image_path).exists() {
|
||||
return Err(format!("File not found: {}", image_path).into());
|
||||
}
|
||||
|
||||
let upload_response = client.upload_image(image_path, false).await?;
|
||||
let uploaded_filename = upload_response.name;
|
||||
println!("✅ Upload successful! Server filename: {}", uploaded_filename);
|
||||
|
||||
// Step 2: Execute AI Model Face & Hair Enhancement
|
||||
println!("\n🔄 Step 2: Executing AI Model Face & Hair Enhancement...");
|
||||
|
||||
// Create execution callbacks
|
||||
let callbacks = Arc::new(
|
||||
SimpleCallbacks::new()
|
||||
.with_progress(|progress| {
|
||||
let percentage = (progress.progress as f64 / progress.max as f64 * 100.0) as u32;
|
||||
println!("⏳ Progress: {}% ({}/{}) - Node: {}",
|
||||
percentage, progress.progress, progress.max, progress.node_id);
|
||||
})
|
||||
.with_executing(|node_id| {
|
||||
println!("🔄 Executing node: {}", node_id);
|
||||
})
|
||||
.with_executed(|result| {
|
||||
println!("✅ Node completed - Prompt ID: {}", result.prompt_id);
|
||||
})
|
||||
.with_error(|error| {
|
||||
println!("❌ Execution error: {}", error.message);
|
||||
})
|
||||
);
|
||||
|
||||
// Prepare parameters
|
||||
let mut parameters = HashMap::new();
|
||||
parameters.insert("input_image".to_string(), json!(uploaded_filename));
|
||||
parameters.insert("face_prompt".to_string(),
|
||||
json!("beautiful woman, perfect skin, detailed facial features, professional photography"));
|
||||
parameters.insert("face_denoise".to_string(), json!("0.25"));
|
||||
|
||||
let execution_options = ExecutionOptions {
|
||||
timeout: Some(std::time::Duration::from_secs(180)), // 3 minutes timeout
|
||||
priority: None,
|
||||
};
|
||||
|
||||
let result = client.execute_template_with_callbacks(
|
||||
template,
|
||||
parameters,
|
||||
execution_options,
|
||||
callbacks,
|
||||
).await?;
|
||||
|
||||
if result.success {
|
||||
println!("\n🎉 AI Model Enhancement completed successfully!");
|
||||
println!("📊 Total execution time: {}ms ({:.1}s)",
|
||||
result.execution_time, result.execution_time as f64 / 1000.0);
|
||||
println!("🆔 Prompt ID: {}", result.prompt_id);
|
||||
|
||||
if let Some(outputs) = &result.outputs {
|
||||
println!("\n📁 Generated outputs:");
|
||||
println!("{}", serde_json::to_string_pretty(outputs)?);
|
||||
|
||||
// Convert outputs to HTTP URLs
|
||||
let image_urls = client.outputs_to_urls(outputs);
|
||||
println!("\n🖼️ Enhanced Image URLs:");
|
||||
for (index, url) in image_urls.iter().enumerate() {
|
||||
println!(" {}. {}", index + 1, url);
|
||||
}
|
||||
|
||||
println!("\n💡 How to use these URLs:");
|
||||
println!(" - Copy the URL and paste in your browser to view");
|
||||
println!(" - Right-click and \"Save As\" to download");
|
||||
println!(" - Use in your application to display the enhanced image");
|
||||
}
|
||||
} else {
|
||||
println!("\n❌ AI Model Enhancement failed!");
|
||||
if let Some(error) = &result.error {
|
||||
println!("Error details: {}", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Show troubleshooting tips based on error
|
||||
fn show_troubleshooting_tips(error: &Box<dyn std::error::Error>) {
|
||||
let error_msg = error.to_string();
|
||||
|
||||
if error_msg.contains("File not found") || error_msg.contains("No such file") {
|
||||
println!("\n💡 Troubleshooting Tips:");
|
||||
println!("1. 🔍 Check if the file path is correct");
|
||||
println!("2. 📁 Make sure the file exists at the specified location");
|
||||
println!("3. 🔐 Ensure you have read permissions for the file");
|
||||
println!("4. 📝 Try using absolute path: /home/user/images/20250808-111737.png");
|
||||
println!("5. 🖼️ Verify the file is a valid image format (PNG, JPG, etc.)");
|
||||
} else if error_msg.contains("Failed to upload") || error_msg.contains("HTTP") {
|
||||
println!("\n💡 Network/Server Issues:");
|
||||
println!("1. 🌐 Check if ComfyUI server is running and accessible");
|
||||
println!("2. 📡 Verify network connection to the server");
|
||||
println!("3. 💾 Check if server has enough disk space");
|
||||
println!("4. 🔒 Ensure server allows file uploads");
|
||||
}
|
||||
}
|
||||
|
||||
/// Alternative method using one-step upload and execute (conceptual)
|
||||
async fn test_one_step_method() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("\n\n🚀 Testing One-Step Method (Upload + Execute)");
|
||||
|
||||
let mut client = ComfyUIClient::new(ComfyUIClientConfig {
|
||||
base_url: "http://192.168.0.193:8188".to_string(),
|
||||
..Default::default()
|
||||
})?;
|
||||
|
||||
client.connect().await?;
|
||||
client.templates().register_from_data(AI_MODEL_FACE_HAIR_FIX_TEMPLATE.clone())?;
|
||||
let template = client.templates_ref()
|
||||
.get_by_id(&AI_MODEL_FACE_HAIR_FIX_TEMPLATE.metadata.id)
|
||||
.ok_or("Failed to register template")?;
|
||||
|
||||
let local_image_path = "20250808-111737.png";
|
||||
|
||||
println!("🔄 One-step upload and execute...");
|
||||
|
||||
// For now, we'll implement this as upload + execute since we don't have
|
||||
// a dedicated one-step method yet
|
||||
match upload_and_execute_one_step(&client, template, local_image_path).await {
|
||||
Ok(_) => println!("🎉 One-step method completed successfully!"),
|
||||
Err(e) => println!("⚠️ One-step method failed: {}", e),
|
||||
}
|
||||
|
||||
client.disconnect().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// One-step upload and execute (simplified version)
|
||||
async fn upload_and_execute_one_step(
|
||||
client: &ComfyUIClient,
|
||||
template: &comfyui_sdk::WorkflowTemplate,
|
||||
image_path: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Upload image
|
||||
let upload_response = client.upload_image(image_path, false).await?;
|
||||
|
||||
// Create simple callbacks
|
||||
let callbacks = Arc::new(
|
||||
SimpleCallbacks::new()
|
||||
.with_progress(|progress| {
|
||||
let percentage = (progress.progress as f64 / progress.max as f64 * 100.0) as u32;
|
||||
println!("⏳ Progress: {}% - Node: {}", percentage, progress.node_id);
|
||||
})
|
||||
);
|
||||
|
||||
// Execute template
|
||||
let mut parameters = HashMap::new();
|
||||
parameters.insert("input_image".to_string(), json!(upload_response.name));
|
||||
parameters.insert("face_prompt".to_string(),
|
||||
json!("stunning model, flawless skin, professional photography, high quality"));
|
||||
parameters.insert("face_denoise".to_string(), json!("0.3"));
|
||||
|
||||
let result = client.execute_template_with_callbacks(
|
||||
template,
|
||||
parameters,
|
||||
ExecutionOptions {
|
||||
timeout: Some(std::time::Duration::from_secs(180)),
|
||||
priority: None,
|
||||
},
|
||||
callbacks,
|
||||
).await?;
|
||||
|
||||
if result.success {
|
||||
if let Some(outputs) = &result.outputs {
|
||||
let image_urls = client.outputs_to_urls(outputs);
|
||||
println!("🖼️ Result URLs: {:?}", image_urls);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Show usage examples
|
||||
fn show_usage_examples() {
|
||||
println!("\n📚 Complete Usage Guide for Local Images:");
|
||||
println!("");
|
||||
println!("🔧 Basic Setup:");
|
||||
println!("```rust");
|
||||
println!("use comfyui_sdk::{{ComfyUIClient, ComfyUIClientConfig, AI_MODEL_FACE_HAIR_FIX_TEMPLATE}};");
|
||||
println!("");
|
||||
println!("let mut client = ComfyUIClient::new(ComfyUIClientConfig {{");
|
||||
println!(" base_url: \"http://your-comfyui-server:8188\".to_string(),");
|
||||
println!(" ..Default::default()");
|
||||
println!("}});");
|
||||
println!("```");
|
||||
println!("");
|
||||
println!("📤 Method 1 - Manual Upload:");
|
||||
println!("```rust");
|
||||
println!("// Upload image first");
|
||||
println!("let upload_response = client.upload_image(");
|
||||
println!(" \"/path/to/your/image.png\", false");
|
||||
println!(").await?;");
|
||||
println!("");
|
||||
println!("// Then execute template");
|
||||
println!("let result = client.execute_template(template, parameters, options).await?;");
|
||||
println!("```");
|
||||
println!("");
|
||||
println!("🚀 Method 2 - Combined Upload and Execute:");
|
||||
println!("```rust");
|
||||
println!("// Upload and execute in sequence");
|
||||
println!("let upload_response = client.upload_image(image_path, false).await?;");
|
||||
println!("let mut parameters = HashMap::new();");
|
||||
println!("parameters.insert(\"input_image\".to_string(), json!(upload_response.name));");
|
||||
println!("let result = client.execute_template_with_callbacks(");
|
||||
println!(" template, parameters, options, callbacks");
|
||||
println!(").await?;");
|
||||
println!("```");
|
||||
}
|
||||
|
||||
/// Run all tests
|
||||
async fn run_all_tests() -> Result<(), Box<dyn std::error::Error>> {
|
||||
show_usage_examples();
|
||||
test_with_real_local_image().await?;
|
||||
// Uncomment to test one-step method as well
|
||||
// test_one_step_method().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_all_tests().await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_show_usage_examples() {
|
||||
show_usage_examples();
|
||||
// This test just ensures the function runs without panicking
|
||||
}
|
||||
}
|
||||
162
cargos/comfyui-sdk/examples/simple_local_image.rs
Normal file
162
cargos/comfyui-sdk/examples/simple_local_image.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
//! Simple Local Image Upload and Enhancement
|
||||
//!
|
||||
//! A simplified version that focuses on the core functionality:
|
||||
//! 1. Upload a local image to ComfyUI server
|
||||
//! 2. Execute AI Model Face & Hair enhancement
|
||||
//! 3. Get the enhanced image URLs
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use comfyui_sdk::{
|
||||
ComfyUIClient, ComfyUIClientConfig, ExecutionOptions,
|
||||
AI_MODEL_FACE_HAIR_FIX_TEMPLATE
|
||||
};
|
||||
use comfyui_sdk::utils::SimpleCallbacks;
|
||||
use serde_json::json;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("🚀 Simple Local Image Enhancement Test");
|
||||
|
||||
// Configuration
|
||||
let server_url = "http://192.168.0.193:8188";
|
||||
let image_path = "20250808-111737.png";
|
||||
|
||||
// Step 1: Initialize client and connect
|
||||
println!("📡 Connecting to ComfyUI server at {}...", server_url);
|
||||
let mut client = ComfyUIClient::new(ComfyUIClientConfig {
|
||||
base_url: server_url.to_string(),
|
||||
..Default::default()
|
||||
})?;
|
||||
|
||||
client.connect().await?;
|
||||
println!("✅ Connected!");
|
||||
|
||||
// Step 2: Register template
|
||||
println!("📝 Registering AI enhancement template...");
|
||||
client.templates().register_from_data(AI_MODEL_FACE_HAIR_FIX_TEMPLATE.clone())?;
|
||||
let template = client.templates_ref()
|
||||
.get_by_id("ai-model-face-hair-fix")
|
||||
.ok_or("Template not found")?;
|
||||
println!("✅ Template ready!");
|
||||
|
||||
// Step 3: Check if image exists
|
||||
if !Path::new(image_path).exists() {
|
||||
println!("❌ Image file not found: {}", image_path);
|
||||
println!("💡 Please make sure the file exists in the current directory");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Step 4: Upload image
|
||||
println!("📤 Uploading image: {}...", image_path);
|
||||
let upload_response = client.upload_image(image_path, false).await?;
|
||||
println!("✅ Upload successful! Server filename: {}", upload_response.name);
|
||||
|
||||
// Step 5: Setup progress callbacks
|
||||
let callbacks = Arc::new(
|
||||
SimpleCallbacks::new()
|
||||
.with_progress(|progress| {
|
||||
let percentage = (progress.progress as f64 / progress.max as f64 * 100.0) as u32;
|
||||
println!("⏳ Progress: {}%", percentage);
|
||||
})
|
||||
.with_executing(|node_id| {
|
||||
println!("🔄 Processing node: {}", node_id);
|
||||
})
|
||||
.with_error(|error| {
|
||||
println!("❌ Error: {}", error.message);
|
||||
})
|
||||
);
|
||||
|
||||
// Step 6: Execute enhancement
|
||||
println!("🎨 Starting AI enhancement...");
|
||||
let mut parameters = HashMap::new();
|
||||
parameters.insert("input_image".to_string(), json!(upload_response.name));
|
||||
parameters.insert("face_prompt".to_string(),
|
||||
json!("beautiful woman, perfect skin, detailed facial features"));
|
||||
parameters.insert("face_denoise".to_string(), json!("0.25"));
|
||||
|
||||
let result = client.execute_template_with_callbacks(
|
||||
template,
|
||||
parameters,
|
||||
ExecutionOptions {
|
||||
timeout: Some(std::time::Duration::from_secs(180)),
|
||||
priority: None,
|
||||
},
|
||||
callbacks,
|
||||
).await?;
|
||||
|
||||
// Step 7: Show results
|
||||
if result.success {
|
||||
println!("\n🎉 Enhancement completed successfully!");
|
||||
println!("⏱️ Execution time: {:.1}s", result.execution_time as f64 / 1000.0);
|
||||
|
||||
if let Some(outputs) = &result.outputs {
|
||||
let image_urls = client.outputs_to_urls(outputs);
|
||||
|
||||
if !image_urls.is_empty() {
|
||||
println!("\n🖼️ Enhanced image URLs:");
|
||||
for (i, url) in image_urls.iter().enumerate() {
|
||||
println!(" {}. {}", i + 1, url);
|
||||
}
|
||||
|
||||
println!("\n💡 To view your enhanced image:");
|
||||
println!(" - Copy the URL above and paste it in your browser");
|
||||
println!(" - Right-click and 'Save As' to download the image");
|
||||
} else {
|
||||
println!("⚠️ No image URLs found in outputs");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("❌ Enhancement failed!");
|
||||
if let Some(error) = &result.error {
|
||||
println!("Error: {}", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 8: Cleanup
|
||||
client.disconnect().await?;
|
||||
println!("\n👋 Disconnected from server");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Helper function to validate image file
|
||||
fn validate_image_file(path: &str) -> Result<(), String> {
|
||||
let path = Path::new(path);
|
||||
|
||||
if !path.exists() {
|
||||
return Err(format!("File does not exist: {}", path.display()));
|
||||
}
|
||||
|
||||
if !path.is_file() {
|
||||
return Err(format!("Path is not a file: {}", path.display()));
|
||||
}
|
||||
|
||||
// Check file extension
|
||||
if let Some(extension) = path.extension() {
|
||||
let ext = extension.to_string_lossy().to_lowercase();
|
||||
match ext.as_str() {
|
||||
"png" | "jpg" | "jpeg" | "bmp" | "tiff" | "webp" => Ok(()),
|
||||
_ => Err(format!("Unsupported image format: {}", ext)),
|
||||
}
|
||||
} else {
|
||||
Err("File has no extension".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_validate_image_file() {
|
||||
// Test with non-existent file
|
||||
assert!(validate_image_file("non_existent.png").is_err());
|
||||
|
||||
// Test with valid extensions
|
||||
// Note: These tests would pass if the files existed
|
||||
// assert!(validate_image_file("test.png").is_ok());
|
||||
// assert!(validate_image_file("test.jpg").is_ok());
|
||||
}
|
||||
}
|
||||
113
cargos/comfyui-sdk/examples/test_url_fix.rs
Normal file
113
cargos/comfyui-sdk/examples/test_url_fix.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
//! Test URL generation fix
|
||||
//!
|
||||
//! This example demonstrates that the double slash bug has been fixed
|
||||
|
||||
use std::collections::HashMap;
|
||||
use serde_json::json;
|
||||
use comfyui_sdk::{HTTPClient, ComfyUIClientConfig};
|
||||
|
||||
fn main() {
|
||||
println!("🔧 Testing URL Generation Fix");
|
||||
println!("==============================");
|
||||
|
||||
// Test with base URL ending with slash (the problematic case)
|
||||
let config_with_slash = ComfyUIClientConfig {
|
||||
base_url: "http://192.168.0.193:8188/".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
let client_with_slash = HTTPClient::new(config_with_slash).unwrap();
|
||||
|
||||
// Test with base URL without ending slash
|
||||
let config_without_slash = ComfyUIClientConfig {
|
||||
base_url: "http://192.168.0.193:8188".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
let client_without_slash = HTTPClient::new(config_without_slash).unwrap();
|
||||
|
||||
// Create test outputs (simulating ComfyUI response)
|
||||
let mut outputs = HashMap::new();
|
||||
outputs.insert("58".to_string(), json!({
|
||||
"images": [
|
||||
{
|
||||
"filename": "ComfyUI_02046_.png",
|
||||
"subfolder": "",
|
||||
"type": "output"
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
println!("\n📋 Test Case 1: Base URL with trailing slash");
|
||||
println!("Base URL: http://192.168.0.193:8188/");
|
||||
let urls_with_slash = client_with_slash.outputs_to_urls(&outputs);
|
||||
for url in &urls_with_slash {
|
||||
println!("Generated URL: {}", url);
|
||||
if url.contains("//view") {
|
||||
println!("❌ FAILED: URL contains double slash!");
|
||||
} else {
|
||||
println!("✅ PASSED: No double slash found");
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n📋 Test Case 2: Base URL without trailing slash");
|
||||
println!("Base URL: http://192.168.0.193:8188");
|
||||
let urls_without_slash = client_without_slash.outputs_to_urls(&outputs);
|
||||
for url in &urls_without_slash {
|
||||
println!("Generated URL: {}", url);
|
||||
if url.contains("//view") {
|
||||
println!("❌ FAILED: URL contains double slash!");
|
||||
} else {
|
||||
println!("✅ PASSED: No double slash found");
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n📋 Test Case 3: Multiple images");
|
||||
let mut multi_outputs = HashMap::new();
|
||||
multi_outputs.insert("58".to_string(), json!({
|
||||
"images": [
|
||||
{
|
||||
"filename": "image1.png",
|
||||
"subfolder": "temp",
|
||||
"type": "output"
|
||||
},
|
||||
{
|
||||
"filename": "image2.jpg",
|
||||
"subfolder": "",
|
||||
"type": "temp"
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
let multi_urls = client_with_slash.outputs_to_urls(&multi_outputs);
|
||||
println!("Generated {} URLs:", multi_urls.len());
|
||||
for (i, url) in multi_urls.iter().enumerate() {
|
||||
println!(" {}. {}", i + 1, url);
|
||||
if url.contains("//view") {
|
||||
println!(" ❌ FAILED: URL contains double slash!");
|
||||
} else {
|
||||
println!(" ✅ PASSED: No double slash found");
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n🎯 Expected vs Actual URLs:");
|
||||
println!("Expected: http://192.168.0.193:8188/view?filename=ComfyUI_02046_.png&subfolder=&type=output");
|
||||
if !urls_with_slash.is_empty() {
|
||||
println!("Actual: {}", urls_with_slash[0]);
|
||||
if urls_with_slash[0] == "http://192.168.0.193:8188/view?filename=ComfyUI_02046_.png&subfolder=&type=output" {
|
||||
println!("✅ URLs match perfectly!");
|
||||
} else {
|
||||
println!("❌ URLs don't match!");
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n🔍 Consistency Check:");
|
||||
if urls_with_slash == urls_without_slash {
|
||||
println!("✅ URLs are consistent regardless of trailing slash in base URL");
|
||||
} else {
|
||||
println!("❌ URLs are inconsistent!");
|
||||
println!("With slash: {:?}", urls_with_slash);
|
||||
println!("Without slash: {:?}", urls_without_slash);
|
||||
}
|
||||
|
||||
println!("\n🎉 URL Generation Fix Test Completed!");
|
||||
println!("The double slash bug has been fixed. URLs now correctly use single slashes.");
|
||||
}
|
||||
Reference in New Issue
Block a user