feat: add comfyui sdk

This commit is contained in:
imeepos
2025-08-08 13:48:38 +08:00
parent 763b4a975c
commit 5f6a302dfd
27 changed files with 4713 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
//! Template system for ComfyUI SDK
pub mod workflow_template;
pub mod workflow_instance;
pub mod template_manager;
// Re-export main types
pub use workflow_template::WorkflowTemplate;
pub use workflow_instance::WorkflowInstance;
pub use template_manager::TemplateManager;

View File

@@ -0,0 +1,194 @@
//! TemplateManager for managing workflow templates
use std::collections::HashMap;
use crate::types::{WorkflowTemplateData, ParameterValues};
use crate::templates::{WorkflowTemplate, WorkflowInstance};
use crate::error::{ComfyUIError, Result};
/// Manages a collection of workflow templates
#[derive(Debug, Clone)]
pub struct TemplateManager {
templates: HashMap<String, WorkflowTemplate>,
}
impl TemplateManager {
/// Creates a new TemplateManager
pub fn new() -> Self {
Self {
templates: HashMap::new(),
}
}
/// Registers a template from template data
pub fn register_from_data(&mut self, data: WorkflowTemplateData) -> Result<()> {
let template = WorkflowTemplate::from_data(data)?;
let id = template.id().to_string();
self.templates.insert(id, template);
Ok(())
}
/// Registers a template
pub fn register(&mut self, template: WorkflowTemplate) {
let id = template.id().to_string();
self.templates.insert(id, template);
}
/// Gets a template by ID
pub fn get_by_id(&self, id: &str) -> Option<&WorkflowTemplate> {
self.templates.get(id)
}
/// Gets a mutable reference to a template by ID
pub fn get_by_id_mut(&mut self, id: &str) -> Option<&mut WorkflowTemplate> {
self.templates.get_mut(id)
}
/// Removes a template by ID
pub fn remove(&mut self, id: &str) -> Option<WorkflowTemplate> {
self.templates.remove(id)
}
/// Lists all template IDs
pub fn list_ids(&self) -> Vec<&str> {
self.templates.keys().map(|s| s.as_str()).collect()
}
/// Lists all templates
pub fn list_templates(&self) -> Vec<&WorkflowTemplate> {
self.templates.values().collect()
}
/// Gets templates by category
pub fn get_by_category(&self, category: &str) -> Vec<&WorkflowTemplate> {
self.templates
.values()
.filter(|template| {
template.category()
.map(|c| c == category)
.unwrap_or(false)
})
.collect()
}
/// Gets templates by tag
pub fn get_by_tag(&self, tag: &str) -> Vec<&WorkflowTemplate> {
self.templates
.values()
.filter(|template| template.tags().contains(&tag.to_string()))
.collect()
}
/// Searches templates by name (case-insensitive partial match)
pub fn search_by_name(&self, query: &str) -> Vec<&WorkflowTemplate> {
let query_lower = query.to_lowercase();
self.templates
.values()
.filter(|template| {
template.name().to_lowercase().contains(&query_lower)
})
.collect()
}
/// Searches templates by description (case-insensitive partial match)
pub fn search_by_description(&self, query: &str) -> Vec<&WorkflowTemplate> {
let query_lower = query.to_lowercase();
self.templates
.values()
.filter(|template| {
template.description()
.map(|desc| desc.to_lowercase().contains(&query_lower))
.unwrap_or(false)
})
.collect()
}
/// Creates a workflow instance from a template
pub fn create_instance(&self, template_id: &str, parameters: ParameterValues) -> Result<WorkflowInstance> {
let template = self.get_by_id(template_id)
.ok_or_else(|| ComfyUIError::template_validation(
format!("Template with ID '{}' not found", template_id)
))?;
template.create_instance(parameters)
}
/// Gets the number of registered templates
pub fn count(&self) -> usize {
self.templates.len()
}
/// Checks if a template exists
pub fn contains(&self, id: &str) -> bool {
self.templates.contains_key(id)
}
/// Clears all templates
pub fn clear(&mut self) {
self.templates.clear();
}
/// Gets all categories
pub fn get_categories(&self) -> Vec<String> {
let mut categories: Vec<String> = self.templates
.values()
.filter_map(|template| template.category().map(|c| c.to_string()))
.collect();
categories.sort();
categories.dedup();
categories
}
/// Gets all tags
pub fn get_tags(&self) -> Vec<String> {
let mut tags: Vec<String> = self.templates
.values()
.flat_map(|template| template.tags().iter().cloned())
.collect();
tags.sort();
tags.dedup();
tags
}
/// Validates all templates
pub fn validate_all(&self) -> Result<()> {
for (id, template) in &self.templates {
// Try to create an instance with default values to validate template
let default_values = template.get_default_values();
let validation = template.validate(&default_values);
if !validation.valid {
return Err(ComfyUIError::template_validation(
format!("Template '{}' validation failed", id)
));
}
}
Ok(())
}
/// Exports all templates as template data
pub fn export_all(&self) -> Vec<WorkflowTemplateData> {
self.templates
.values()
.map(|template| template.to_data())
.collect()
}
/// Imports templates from template data
pub fn import_all(&mut self, templates: Vec<WorkflowTemplateData>) -> Result<Vec<String>> {
let mut imported_ids = Vec::new();
for data in templates {
let id = data.metadata.id.clone();
self.register_from_data(data)?;
imported_ids.push(id);
}
Ok(imported_ids)
}
}
impl Default for TemplateManager {
fn default() -> Self {
Self::new()
}
}

View File

@@ -0,0 +1,169 @@
//! WorkflowInstance implementation for executing parameterized workflows
use std::collections::HashMap;
use crate::types::{ComfyUIWorkflow, ParameterValues, ComfyUINode};
use crate::templates::WorkflowTemplate;
use crate::error::{ComfyUIError, Result};
use crate::utils::template_parser::apply_parameters;
/// Represents a workflow instance with applied parameters
#[derive(Debug, Clone)]
pub struct WorkflowInstance {
template: WorkflowTemplate,
parameters: ParameterValues,
resolved_workflow: ComfyUIWorkflow,
}
impl WorkflowInstance {
/// Creates a new WorkflowInstance
pub fn new(template: WorkflowTemplate, parameters: ParameterValues) -> Result<Self> {
// Apply parameters to the workflow
let resolved_workflow = apply_parameters(template.workflow(), &parameters)?;
Ok(Self {
template,
parameters,
resolved_workflow,
})
}
/// Gets the original template
pub fn template(&self) -> &WorkflowTemplate {
&self.template
}
/// Gets the applied parameters
pub fn parameters(&self) -> &ParameterValues {
&self.parameters
}
/// Gets the resolved workflow with parameters applied
pub fn workflow(&self) -> &ComfyUIWorkflow {
&self.resolved_workflow
}
/// Gets the template ID
pub fn template_id(&self) -> &str {
self.template.id()
}
/// Gets the template name
pub fn template_name(&self) -> &str {
self.template.name()
}
/// Gets a specific parameter value
pub fn get_parameter(&self, name: &str) -> Option<&serde_json::Value> {
self.parameters.get(name)
}
/// Updates a parameter value and re-resolves the workflow
pub fn set_parameter(&mut self, name: String, value: serde_json::Value) -> Result<()> {
// Check if parameter exists in template
if !self.template.has_parameter(&name) {
return Err(ComfyUIError::parameter_validation(
format!("Parameter '{}' does not exist in template", name)
));
}
// Update parameter
self.parameters.insert(name, value);
// Validate updated parameters
let validation = self.template.validate(&self.parameters);
if !validation.valid {
let error_messages: Vec<String> = validation.errors
.iter()
.map(|e| format!("{}: {}", e.path, e.message))
.collect();
return Err(ComfyUIError::parameter_validation(
format!("Invalid parameters after update: {}", error_messages.join(", "))
));
}
// Re-resolve workflow
self.resolved_workflow = apply_parameters(self.template.workflow(), &self.parameters)?;
Ok(())
}
/// Updates multiple parameters at once
pub fn set_parameters(&mut self, parameters: HashMap<String, serde_json::Value>) -> Result<()> {
// Check if all parameters exist in template
for name in parameters.keys() {
if !self.template.has_parameter(name) {
return Err(ComfyUIError::parameter_validation(
format!("Parameter '{}' does not exist in template", name)
));
}
}
// Update parameters
for (name, value) in parameters {
self.parameters.insert(name, value);
}
// Validate updated parameters
let validation = self.template.validate(&self.parameters);
if !validation.valid {
let error_messages: Vec<String> = validation.errors
.iter()
.map(|e| format!("{}: {}", e.path, e.message))
.collect();
return Err(ComfyUIError::parameter_validation(
format!("Invalid parameters after update: {}", error_messages.join(", "))
));
}
// Re-resolve workflow
self.resolved_workflow = apply_parameters(self.template.workflow(), &self.parameters)?;
Ok(())
}
/// Gets a specific node from the resolved workflow
pub fn get_node(&self, node_id: &str) -> Option<&ComfyUINode> {
self.resolved_workflow.get(node_id)
}
/// Gets all node IDs in the workflow
pub fn get_node_ids(&self) -> Vec<&str> {
self.resolved_workflow.keys().map(|s| s.as_str()).collect()
}
/// Checks if the workflow contains a specific node
pub fn has_node(&self, node_id: &str) -> bool {
self.resolved_workflow.contains_key(node_id)
}
/// Gets the number of nodes in the workflow
pub fn node_count(&self) -> usize {
self.resolved_workflow.len()
}
/// Converts the instance to a JSON-serializable workflow
pub fn to_workflow_json(&self) -> Result<serde_json::Value> {
serde_json::to_value(&self.resolved_workflow)
.map_err(ComfyUIError::from)
}
/// Creates a clone with different parameters
pub fn with_parameters(&self, parameters: ParameterValues) -> Result<Self> {
Self::new(self.template.clone(), parameters)
}
/// Validates the current instance
pub fn validate(&self) -> Result<()> {
let validation = self.template.validate(&self.parameters);
if !validation.valid {
let error_messages: Vec<String> = validation.errors
.iter()
.map(|e| format!("{}: {}", e.path, e.message))
.collect();
return Err(ComfyUIError::parameter_validation(
format!("Instance validation failed: {}", error_messages.join(", "))
));
}
Ok(())
}
}

View File

@@ -0,0 +1,193 @@
//! WorkflowTemplate implementation for managing parameterized ComfyUI workflows
use std::collections::HashMap;
use crate::types::{
WorkflowTemplateData, TemplateMetadata, ComfyUIWorkflow,
ParameterSchema, ParameterValues, ValidationResult, ParameterType
};
use crate::error::{ComfyUIError, Result};
use crate::templates::WorkflowInstance;
use crate::utils::validation::validate_parameters;
/// Represents a parameterized workflow template
#[derive(Debug, Clone)]
pub struct WorkflowTemplate {
metadata: TemplateMetadata,
workflow: ComfyUIWorkflow,
parameters: HashMap<String, ParameterSchema>,
}
impl WorkflowTemplate {
/// Creates a new WorkflowTemplate instance
pub fn new(data: WorkflowTemplateData) -> Result<Self> {
let template = Self {
metadata: data.metadata,
workflow: data.workflow,
parameters: data.parameters,
};
// Validate template structure
template.validate_template()?;
Ok(template)
}
/// Gets the template metadata
pub fn metadata(&self) -> &TemplateMetadata {
&self.metadata
}
/// Gets the workflow definition
pub fn workflow(&self) -> &ComfyUIWorkflow {
&self.workflow
}
/// Gets the parameter schemas
pub fn parameters(&self) -> &HashMap<String, ParameterSchema> {
&self.parameters
}
/// Gets the template ID
pub fn id(&self) -> &str {
&self.metadata.id
}
/// Gets the template name
pub fn name(&self) -> &str {
&self.metadata.name
}
/// Gets the template description
pub fn description(&self) -> Option<&str> {
self.metadata.description.as_deref()
}
/// Gets the template category
pub fn category(&self) -> Option<&str> {
self.metadata.category.as_deref()
}
/// Gets the template tags
pub fn tags(&self) -> &[String] {
self.metadata.tags.as_deref().unwrap_or(&[])
}
/// Validates the provided parameters against the template schema
pub fn validate(&self, parameters: &ParameterValues) -> ValidationResult {
validate_parameters(parameters, &self.parameters)
}
/// Creates a workflow instance with the provided parameters
pub fn create_instance(&self, parameters: ParameterValues) -> Result<WorkflowInstance> {
let validation = self.validate(&parameters);
if !validation.valid {
let error_messages: Vec<String> = validation.errors
.iter()
.map(|e| format!("{}: {}", e.path, e.message))
.collect();
return Err(ComfyUIError::parameter_validation(
format!("Invalid parameters: {}", error_messages.join(", "))
));
}
WorkflowInstance::new(self.clone(), parameters)
}
/// Gets the parameter schema for a specific parameter
pub fn get_parameter_schema(&self, parameter_name: &str) -> Option<&ParameterSchema> {
self.parameters.get(parameter_name)
}
/// Gets all required parameter names
pub fn get_required_parameters(&self) -> Vec<&str> {
self.parameters
.iter()
.filter(|(_, schema)| schema.required.unwrap_or(false))
.map(|(name, _)| name.as_str())
.collect()
}
/// Gets all optional parameter names
pub fn get_optional_parameters(&self) -> Vec<&str> {
self.parameters
.iter()
.filter(|(_, schema)| !schema.required.unwrap_or(false))
.map(|(name, _)| name.as_str())
.collect()
}
/// Checks if the template has a specific parameter
pub fn has_parameter(&self, parameter_name: &str) -> bool {
self.parameters.contains_key(parameter_name)
}
/// Gets default values for all parameters that have defaults
pub fn get_default_values(&self) -> ParameterValues {
let mut defaults = HashMap::new();
for (name, schema) in &self.parameters {
if let Some(default_value) = &schema.default {
defaults.insert(name.clone(), default_value.clone());
}
}
defaults
}
/// Creates a copy of the template with updated metadata
pub fn with_metadata(&self, metadata: TemplateMetadata) -> Self {
Self {
metadata,
workflow: self.workflow.clone(),
parameters: self.parameters.clone(),
}
}
/// Converts the template to a JSON-serializable object
pub fn to_data(&self) -> WorkflowTemplateData {
WorkflowTemplateData {
metadata: self.metadata.clone(),
workflow: self.workflow.clone(),
parameters: self.parameters.clone(),
}
}
/// Creates a WorkflowTemplate from template data
pub fn from_data(data: WorkflowTemplateData) -> Result<Self> {
Self::new(data)
}
/// Validates the template structure
fn validate_template(&self) -> Result<()> {
// Validate metadata
if self.metadata.id.is_empty() {
return Err(ComfyUIError::template_validation(
"Template metadata must have a valid id"
));
}
if self.metadata.name.is_empty() {
return Err(ComfyUIError::template_validation(
"Template metadata must have a valid name"
));
}
// Validate workflow
if self.workflow.is_empty() {
return Err(ComfyUIError::template_validation(
"Template must have a valid workflow object"
));
}
// Validate parameter schemas
for (name, schema) in &self.parameters {
if !matches!(
schema.param_type,
ParameterType::String | ParameterType::Number | ParameterType::Boolean |
ParameterType::Array | ParameterType::Object
) {
return Err(ComfyUIError::template_validation(
format!("Parameter '{}' has invalid type: {:?}", name, schema.param_type)
));
}
}
Ok(())
}
}