# ComfyUI Workflow Documentation

This guide covers how to create, structure, and execute ComfyUI workflows in the
Oshun platform.

## Table of Contents

- [Workflow Overview](#workflow-overview)
- [Workflow Structure](#workflow-structure)
- [Creating Workflows](#creating-workflows)
- [Built-in Generation Types](#built-in-generation-types)
- [Custom Workflows](#custom-workflows)
- [Parameter Injection](#parameter-injection)
- [Workflow Validation](#workflow-validation)
- [Workflow Templates](#workflow-templates)
- [Best Practices](#best-practices)

---

## Workflow Overview

ComfyUI workflows are node-based graphs that define image generation pipelines.
Each workflow consists of interconnected nodes that process data sequentially or
in parallel.

### Architecture

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                        ComfyUI Workflow Execution                            │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  Input                   Processing                    Output               │
│  ┌──────────────┐       ┌──────────────┐             ┌──────────────┐      │
│  │   Prompt     │──────▶│  CLIP Encode │──┐          │              │      │
│  │   (Text)     │       └──────────────┘  │          │   Save       │      │
│  └──────────────┘                         │          │   Image      │      │
│                                           ▼          │              │      │
│  ┌──────────────┐       ┌──────────────┐ ┌────────┐  │              │      │
│  │  Checkpoint  │──────▶│  KSampler    │─┤ VAE    ├─▶│              │      │
│  │  Loader      │       └──────────────┘ │ Decode │  │              │      │
│  └──────────────┘                    ▲   └────────┘  └──────────────┘      │
│                                      │                                      │
│  ┌──────────────┐       ┌──────────────┐                                   │
│  │  Empty       │──────▶│  Latent      │─┘                                 │
│  │  Latent      │       │  Image       │                                   │
│  └──────────────┘       └──────────────┘                                   │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

### Workflow Components

| Component   | Description                                       |
| ----------- | ------------------------------------------------- |
| **Nodes**   | Processing units that perform specific operations |
| **Edges**   | Connections between node outputs and inputs       |
| **Inputs**  | External values injected into the workflow        |
| **Outputs** | Generated results (images, videos, data)          |

---

## Workflow Structure

### JSON Format

ComfyUI workflows use a JSON format with numbered nodes:

```json
{
  "3": {
    "class_type": "KSampler",
    "inputs": {
      "seed": 12345,
      "steps": 20,
      "cfg": 7.5,
      "sampler_name": "euler_ancestral",
      "scheduler": "normal",
      "denoise": 1.0,
      "model": ["4", 0],
      "positive": ["6", 0],
      "negative": ["7", 0],
      "latent_image": ["5", 0]
    }
  },
  "4": {
    "class_type": "CheckpointLoaderSimple",
    "inputs": {
      "ckpt_name": "sd_xl_base_1.0.safetensors"
    }
  },
  "5": {
    "class_type": "EmptyLatentImage",
    "inputs": {
      "width": 1024,
      "height": 1024,
      "batch_size": 1
    }
  },
  "6": {
    "class_type": "CLIPTextEncode",
    "inputs": {
      "text": "A beautiful sunset over mountains",
      "clip": ["4", 1]
    }
  },
  "7": {
    "class_type": "CLIPTextEncode",
    "inputs": {
      "text": "blurry, low quality",
      "clip": ["4", 1]
    }
  },
  "8": {
    "class_type": "VAEDecode",
    "inputs": {
      "samples": ["3", 0],
      "vae": ["4", 2]
    }
  },
  "9": {
    "class_type": "SaveImage",
    "inputs": {
      "filename_prefix": "ComfyUI",
      "images": ["8", 0]
    }
  }
}
```

### Node Reference Format

Connections use the format `["node_id", output_index]`:

```typescript
// Connection from node 4, output slot 0
"model": ["4", 0]

// Connection from node 6, output slot 0
"positive": ["6", 0]
```

### TypeScript Interface

```typescript
interface ComfyWorkflow {
  nodes: Record<string, ComfyNode>;
  metadata?: WorkflowMetadata;
}

interface ComfyNode {
  class_type: string;
  inputs: Record<string, ComfyNodeInput>;
  _meta?: Record<string, unknown>;
}

type ComfyNodeInput =
  | string
  | number
  | boolean
  | [string, number] // Node reference [nodeId, outputIndex]
  | ComfyNodeInput[];

interface WorkflowMetadata {
  title?: string;
  description?: string;
  author?: string;
  version?: string;
  tags?: string[];
}
```

---

## Creating Workflows

### Using ComfyUI Editor

1. **Open ComfyUI** in your browser
2. **Build your workflow** using the node editor
3. **Test the workflow** with sample inputs
4. **Export** via "Save (API Format)" or the developer console

### Programmatic Creation

```typescript
import { ComfyWorkflow, ComfyNode } from '@oshun/comfy-types';

// Build a simple txt2img workflow
function buildTxt2ImgWorkflow(params: {
  prompt: string;
  negativePrompt: string;
  model: string;
  width: number;
  height: number;
  steps: number;
  cfg: number;
  seed: number;
}): ComfyWorkflow {
  return {
    nodes: {
      // Checkpoint loader
      '1': {
        class_type: 'CheckpointLoaderSimple',
        inputs: {
          ckpt_name: params.model,
        },
      },
      // Empty latent image
      '2': {
        class_type: 'EmptyLatentImage',
        inputs: {
          width: params.width,
          height: params.height,
          batch_size: 1,
        },
      },
      // Positive prompt encoding
      '3': {
        class_type: 'CLIPTextEncode',
        inputs: {
          text: params.prompt,
          clip: ['1', 1], // CLIP from checkpoint
        },
      },
      // Negative prompt encoding
      '4': {
        class_type: 'CLIPTextEncode',
        inputs: {
          text: params.negativePrompt,
          clip: ['1', 1],
        },
      },
      // Sampler
      '5': {
        class_type: 'KSampler',
        inputs: {
          seed: params.seed,
          steps: params.steps,
          cfg: params.cfg,
          sampler_name: 'euler_ancestral',
          scheduler: 'normal',
          denoise: 1.0,
          model: ['1', 0], // Model from checkpoint
          positive: ['3', 0], // Positive conditioning
          negative: ['4', 0], // Negative conditioning
          latent_image: ['2', 0], // Empty latent
        },
      },
      // VAE decode
      '6': {
        class_type: 'VAEDecode',
        inputs: {
          samples: ['5', 0], // Latents from sampler
          vae: ['1', 2], // VAE from checkpoint
        },
      },
      // Save image
      '7': {
        class_type: 'SaveImage',
        inputs: {
          filename_prefix: 'oshun',
          images: ['6', 0],
        },
      },
    },
    metadata: {
      title: 'Simple Txt2Img',
      version: '1.0',
    },
  };
}
```

### Workflow Builder Pattern

```typescript
class WorkflowBuilder {
  private nodeId = 0;
  private nodes: Record<string, ComfyNode> = {};

  private nextId(): string {
    return String(++this.nodeId);
  }

  loadCheckpoint(ckptName: string): {
    model: string;
    clip: string;
    vae: string;
  } {
    const id = this.nextId();
    this.nodes[id] = {
      class_type: 'CheckpointLoaderSimple',
      inputs: { ckpt_name: ckptName },
    };
    return {
      model: `${id}:0`,
      clip: `${id}:1`,
      vae: `${id}:2`,
    };
  }

  encodeText(text: string, clip: string): string {
    const id = this.nextId();
    const [nodeId, outputIdx] = clip.split(':');
    this.nodes[id] = {
      class_type: 'CLIPTextEncode',
      inputs: {
        text,
        clip: [nodeId, parseInt(outputIdx)],
      },
    };
    return `${id}:0`;
  }

  emptyLatent(width: number, height: number, batchSize = 1): string {
    const id = this.nextId();
    this.nodes[id] = {
      class_type: 'EmptyLatentImage',
      inputs: { width, height, batch_size: batchSize },
    };
    return `${id}:0`;
  }

  sample(params: {
    model: string;
    positive: string;
    negative: string;
    latent: string;
    steps: number;
    cfg: number;
    seed: number;
    sampler?: string;
    scheduler?: string;
    denoise?: number;
  }): string {
    const id = this.nextId();
    const parseRef = (ref: string): [string, number] => {
      const [nodeId, idx] = ref.split(':');
      return [nodeId, parseInt(idx)];
    };

    this.nodes[id] = {
      class_type: 'KSampler',
      inputs: {
        seed: params.seed,
        steps: params.steps,
        cfg: params.cfg,
        sampler_name: params.sampler || 'euler_ancestral',
        scheduler: params.scheduler || 'normal',
        denoise: params.denoise ?? 1.0,
        model: parseRef(params.model),
        positive: parseRef(params.positive),
        negative: parseRef(params.negative),
        latent_image: parseRef(params.latent),
      },
    };
    return `${id}:0`;
  }

  decode(samples: string, vae: string): string {
    const id = this.nextId();
    const parseRef = (ref: string): [string, number] => {
      const [nodeId, idx] = ref.split(':');
      return [nodeId, parseInt(idx)];
    };

    this.nodes[id] = {
      class_type: 'VAEDecode',
      inputs: {
        samples: parseRef(samples),
        vae: parseRef(vae),
      },
    };
    return `${id}:0`;
  }

  saveImage(images: string, prefix = 'oshun'): string {
    const id = this.nextId();
    const [nodeId, outputIdx] = images.split(':');
    this.nodes[id] = {
      class_type: 'SaveImage',
      inputs: {
        filename_prefix: prefix,
        images: [nodeId, parseInt(outputIdx)],
      },
    };
    return `${id}:0`;
  }

  build(): ComfyWorkflow {
    return { nodes: this.nodes };
  }
}

// Usage
const builder = new WorkflowBuilder();
const { model, clip, vae } = builder.loadCheckpoint(
  'sd_xl_base_1.0.safetensors'
);
const positive = builder.encodeText('A beautiful sunset', clip);
const negative = builder.encodeText('blurry, low quality', clip);
const latent = builder.emptyLatent(1024, 1024);
const samples = builder.sample({
  model,
  positive,
  negative,
  latent,
  steps: 20,
  cfg: 7.5,
  seed: 12345,
});
const images = builder.decode(samples, vae);
builder.saveImage(images);
const workflow = builder.build();
```

---

## Built-in Generation Types

The Oshun platform provides built-in generation types that automatically
construct optimized workflows.

### Text-to-Image (txt2img)

```typescript
import { ComfyUIService } from '@oshun/comfyui-service';

const service = new ComfyUIService(config);

const result = await service.generateTxt2Img({
  prompt: 'A majestic dragon flying over a castle at sunset',
  negativePrompt: 'blurry, low quality, distorted',
  model: 'sd_xl_base_1.0.safetensors',
  width: 1024,
  height: 1024,
  steps: 30,
  cfg: 7.5,
  seed: -1, // Random seed
  sampler: 'euler_ancestral',
  scheduler: 'normal',
});

console.log('Generated image:', result.outputs[0].url);
```

### Image-to-Image (img2img)

```typescript
const result = await service.generateImg2Img({
  prompt: 'Transform into a watercolor painting',
  negativePrompt: 'photo, realistic',
  image: 'https://example.com/input.png', // or base64
  model: 'sd_xl_base_1.0.safetensors',
  strength: 0.75, // Denoising strength
  width: 1024,
  height: 1024,
  steps: 30,
  cfg: 7.5,
  seed: -1,
});
```

### Inpainting

```typescript
const result = await service.generateInpaint({
  prompt: 'A red sports car',
  negativePrompt: 'blurry',
  image: 'https://example.com/scene.png',
  mask: 'https://example.com/mask.png', // White = inpaint area
  model: 'sd_xl_base_1.0_inpainting.safetensors',
  width: 1024,
  height: 1024,
  steps: 30,
  cfg: 7.5,
  seed: -1,
});
```

### Upscaling

```typescript
const result = await service.generateUpscale({
  image: 'https://example.com/low_res.png',
  upscaler: 'RealESRGAN_x4plus',
  scale: 4,
});
```

---

## Custom Workflows

### Submitting Custom Workflows

```typescript
import { RunComfyProvider } from '@oshun/comfy-provider';

const provider = new RunComfyProvider({
  apiKey: process.env.RUNCOMFY_API_KEY,
});

// Load workflow from file or define inline
const workflow: ComfyWorkflow = {
  nodes: {
    // ... your custom workflow nodes
  },
};

const result = await provider.submitJob({
  workflow,
  inputs: [
    { name: 'positive_prompt', value: 'A beautiful landscape' },
    { name: 'negative_prompt', value: 'ugly, blurry' },
    { name: 'seed', value: 12345 },
  ],
  webhookUrl: 'https://api.myapp.com/webhooks/comfyui',
  priority: 1,
  timeoutMs: 300000,
});

console.log('Job ID:', result.jobId);
console.log('Status:', result.status);
```

### Workflow with ControlNet

```typescript
const controlNetWorkflow: ComfyWorkflow = {
  nodes: {
    // Checkpoint loader
    '1': {
      class_type: 'CheckpointLoaderSimple',
      inputs: { ckpt_name: 'sd_xl_base_1.0.safetensors' },
    },
    // ControlNet loader
    '2': {
      class_type: 'ControlNetLoader',
      inputs: { control_net_name: 'controlnet-canny-sdxl-1.0.safetensors' },
    },
    // Load control image
    '3': {
      class_type: 'LoadImage',
      inputs: { image: 'control_image.png' },
    },
    // Canny edge detection
    '4': {
      class_type: 'CannyEdgePreprocessor',
      inputs: {
        image: ['3', 0],
        low_threshold: 100,
        high_threshold: 200,
        resolution: 1024,
      },
    },
    // Positive prompt
    '5': {
      class_type: 'CLIPTextEncode',
      inputs: { text: 'A futuristic city', clip: ['1', 1] },
    },
    // Negative prompt
    '6': {
      class_type: 'CLIPTextEncode',
      inputs: { text: 'blurry, distorted', clip: ['1', 1] },
    },
    // Apply ControlNet
    '7': {
      class_type: 'ControlNetApply',
      inputs: {
        conditioning: ['5', 0],
        control_net: ['2', 0],
        image: ['4', 0],
        strength: 0.8,
      },
    },
    // Empty latent
    '8': {
      class_type: 'EmptyLatentImage',
      inputs: { width: 1024, height: 1024, batch_size: 1 },
    },
    // Sampler
    '9': {
      class_type: 'KSampler',
      inputs: {
        seed: 12345,
        steps: 30,
        cfg: 7.5,
        sampler_name: 'euler_ancestral',
        scheduler: 'normal',
        denoise: 1.0,
        model: ['1', 0],
        positive: ['7', 0], // ControlNet-enhanced conditioning
        negative: ['6', 0],
        latent_image: ['8', 0],
      },
    },
    // VAE decode
    '10': {
      class_type: 'VAEDecode',
      inputs: { samples: ['9', 0], vae: ['1', 2] },
    },
    // Save
    '11': {
      class_type: 'SaveImage',
      inputs: { filename_prefix: 'controlnet', images: ['10', 0] },
    },
  },
};
```

### Workflow with LoRA

```typescript
const loraWorkflow: ComfyWorkflow = {
  nodes: {
    // Checkpoint loader
    '1': {
      class_type: 'CheckpointLoaderSimple',
      inputs: { ckpt_name: 'sd_xl_base_1.0.safetensors' },
    },
    // LoRA loader
    '2': {
      class_type: 'LoraLoader',
      inputs: {
        model: ['1', 0],
        clip: ['1', 1],
        lora_name: 'my_style_lora.safetensors',
        strength_model: 0.8,
        strength_clip: 0.8,
      },
    },
    // Second LoRA (stacking)
    '3': {
      class_type: 'LoraLoader',
      inputs: {
        model: ['2', 0], // Model from first LoRA
        clip: ['2', 1], // CLIP from first LoRA
        lora_name: 'detail_enhancer.safetensors',
        strength_model: 0.5,
        strength_clip: 0.5,
      },
    },
    // ... rest of workflow using ['3', 0] as model and ['3', 1] as clip
  },
};
```

---

## Parameter Injection

### Input Variables

Define injectable parameters in your workflow:

```typescript
const templateWorkflow: ComfyWorkflow = {
  nodes: {
    '1': {
      class_type: 'CheckpointLoaderSimple',
      inputs: { ckpt_name: '{{model}}' }, // Template variable
    },
    '2': {
      class_type: 'CLIPTextEncode',
      inputs: {
        text: '{{positive_prompt}}',
        clip: ['1', 1],
      },
    },
    '3': {
      class_type: 'CLIPTextEncode',
      inputs: {
        text: '{{negative_prompt}}',
        clip: ['1', 1],
      },
    },
    '4': {
      class_type: 'EmptyLatentImage',
      inputs: {
        width: '{{width}}',
        height: '{{height}}',
        batch_size: 1,
      },
    },
    '5': {
      class_type: 'KSampler',
      inputs: {
        seed: '{{seed}}',
        steps: '{{steps}}',
        cfg: '{{cfg}}',
        sampler_name: '{{sampler}}',
        scheduler: 'normal',
        denoise: 1.0,
        model: ['1', 0],
        positive: ['2', 0],
        negative: ['3', 0],
        latent_image: ['4', 0],
      },
    },
    // ...
  },
};

// Inject values
function injectParameters(
  workflow: ComfyWorkflow,
  params: Record<string, unknown>
): ComfyWorkflow {
  const json = JSON.stringify(workflow);
  let result = json;

  for (const [key, value] of Object.entries(params)) {
    const placeholder = `{{${key}}}`;
    const stringValue =
      typeof value === 'string' ? `"${value}"` : String(value);
    // Handle both quoted and unquoted placeholders
    result = result.replace(new RegExp(`"${placeholder}"`, 'g'), stringValue);
    result = result.replace(new RegExp(placeholder, 'g'), String(value));
  }

  return JSON.parse(result);
}

// Usage
const finalWorkflow = injectParameters(templateWorkflow, {
  model: 'sd_xl_base_1.0.safetensors',
  positive_prompt: 'A beautiful sunset',
  negative_prompt: 'blurry',
  width: 1024,
  height: 1024,
  seed: 12345,
  steps: 30,
  cfg: 7.5,
  sampler: 'euler_ancestral',
});
```

### Dynamic Node Configuration

```typescript
interface WorkflowConfig {
  useRefiner: boolean;
  useUpscaler: boolean;
  useControlNet: boolean;
  controlNetType?: 'canny' | 'depth' | 'pose' | 'scribble';
}

function buildDynamicWorkflow(
  baseParams: GenerationParams,
  config: WorkflowConfig
): ComfyWorkflow {
  const builder = new WorkflowBuilder();

  // Base checkpoint
  const { model, clip, vae } = builder.loadCheckpoint(baseParams.model);

  // Optional ControlNet
  let conditioning = builder.encodeText(baseParams.prompt, clip);
  if (config.useControlNet && config.controlNetType) {
    conditioning = builder.addControlNet(
      conditioning,
      config.controlNetType,
      baseParams.controlImage
    );
  }

  // Sample
  let samples = builder.sample({
    model,
    positive: conditioning,
    negative: builder.encodeText(baseParams.negativePrompt, clip),
    latent: builder.emptyLatent(baseParams.width, baseParams.height),
    steps: baseParams.steps,
    cfg: baseParams.cfg,
    seed: baseParams.seed,
  });

  // Optional refiner
  if (config.useRefiner) {
    const refiner = builder.loadCheckpoint('sd_xl_refiner_1.0.safetensors');
    samples = builder.sample({
      model: refiner.model,
      positive: builder.encodeText(baseParams.prompt, refiner.clip),
      negative: builder.encodeText(baseParams.negativePrompt, refiner.clip),
      latent: samples,
      steps: 10,
      cfg: 7.5,
      seed: baseParams.seed,
      denoise: 0.3,
    });
  }

  // Decode
  let images = builder.decode(samples, vae);

  // Optional upscaler
  if (config.useUpscaler) {
    images = builder.upscale(images, 'RealESRGAN_x4plus', 2);
  }

  builder.saveImage(images);

  return builder.build();
}
```

---

## Workflow Validation

### Schema Validation

```typescript
import { z } from 'zod';

const ComfyNodeInputSchema = z.union([
  z.string(),
  z.number(),
  z.boolean(),
  z.tuple([z.string(), z.number()]), // Node reference
  z.array(z.lazy(() => ComfyNodeInputSchema)),
]);

const ComfyNodeSchema = z.object({
  class_type: z.string().min(1),
  inputs: z.record(ComfyNodeInputSchema),
  _meta: z.record(z.unknown()).optional(),
});

const ComfyWorkflowSchema = z.object({
  nodes: z.record(ComfyNodeSchema),
  metadata: z
    .object({
      title: z.string().optional(),
      description: z.string().optional(),
      author: z.string().optional(),
      version: z.string().optional(),
      tags: z.array(z.string()).optional(),
    })
    .optional(),
});

function validateWorkflow(workflow: unknown): ComfyWorkflow {
  return ComfyWorkflowSchema.parse(workflow);
}
```

### Structural Validation

```typescript
interface ValidationResult {
  valid: boolean;
  errors: ValidationError[];
  warnings: ValidationWarning[];
}

interface ValidationError {
  nodeId: string;
  field: string;
  message: string;
}

interface ValidationWarning {
  nodeId: string;
  message: string;
}

function validateWorkflowStructure(workflow: ComfyWorkflow): ValidationResult {
  const errors: ValidationError[] = [];
  const warnings: ValidationWarning[] = [];
  const nodeIds = new Set(Object.keys(workflow.nodes));

  for (const [nodeId, node] of Object.entries(workflow.nodes)) {
    // Check class_type is valid
    if (!node.class_type) {
      errors.push({
        nodeId,
        field: 'class_type',
        message: 'Node missing class_type',
      });
    }

    // Check input references
    for (const [inputName, inputValue] of Object.entries(node.inputs)) {
      if (Array.isArray(inputValue) && inputValue.length === 2) {
        const [refNodeId, outputIdx] = inputValue;

        if (typeof refNodeId === 'string' && typeof outputIdx === 'number') {
          // This is a node reference
          if (!nodeIds.has(refNodeId)) {
            errors.push({
              nodeId,
              field: inputName,
              message: `Reference to non-existent node: ${refNodeId}`,
            });
          }
        }
      }
    }

    // Warn about common issues
    if (node.class_type === 'KSampler' && !node.inputs.seed) {
      warnings.push({
        nodeId,
        message: 'KSampler missing seed - results will be non-deterministic',
      });
    }
  }

  // Check for output nodes
  const hasOutput = Object.values(workflow.nodes).some(
    (n) => n.class_type === 'SaveImage' || n.class_type === 'PreviewImage'
  );

  if (!hasOutput) {
    warnings.push({
      nodeId: '',
      message: 'Workflow has no output node (SaveImage/PreviewImage)',
    });
  }

  return {
    valid: errors.length === 0,
    errors,
    warnings,
  };
}
```

### Cycle Detection

```typescript
function detectCycles(workflow: ComfyWorkflow): string[][] {
  const cycles: string[][] = [];
  const visited = new Set<string>();
  const recursionStack = new Set<string>();

  function getReferencedNodes(node: ComfyNode): string[] {
    const refs: string[] = [];
    for (const input of Object.values(node.inputs)) {
      if (Array.isArray(input) && input.length === 2) {
        const [refNodeId] = input;
        if (typeof refNodeId === 'string') {
          refs.push(refNodeId);
        }
      }
    }
    return refs;
  }

  function dfs(nodeId: string, path: string[]): void {
    visited.add(nodeId);
    recursionStack.add(nodeId);
    path.push(nodeId);

    const node = workflow.nodes[nodeId];
    if (node) {
      for (const refNodeId of getReferencedNodes(node)) {
        if (!visited.has(refNodeId)) {
          dfs(refNodeId, [...path]);
        } else if (recursionStack.has(refNodeId)) {
          // Found cycle
          const cycleStart = path.indexOf(refNodeId);
          cycles.push([...path.slice(cycleStart), refNodeId]);
        }
      }
    }

    recursionStack.delete(nodeId);
  }

  for (const nodeId of Object.keys(workflow.nodes)) {
    if (!visited.has(nodeId)) {
      dfs(nodeId, []);
    }
  }

  return cycles;
}
```

---

## Workflow Templates

### Template Repository

```typescript
interface WorkflowTemplate {
  id: string;
  name: string;
  description: string;
  category: 'txt2img' | 'img2img' | 'inpaint' | 'upscale' | 'video' | 'custom';
  parameters: TemplateParameter[];
  workflow: ComfyWorkflow;
  requiredModels: string[];
  requiredNodes: string[];
  estimatedTime: number; // seconds
  estimatedVram: number; // GB
}

interface TemplateParameter {
  name: string;
  type: 'string' | 'number' | 'boolean' | 'select' | 'image';
  default?: unknown;
  required: boolean;
  description: string;
  options?: string[]; // For select type
  min?: number; // For number type
  max?: number; // For number type
}

const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [
  {
    id: 'sdxl-txt2img-basic',
    name: 'SDXL Text-to-Image',
    description: 'Basic SDXL text-to-image generation',
    category: 'txt2img',
    parameters: [
      {
        name: 'prompt',
        type: 'string',
        required: true,
        description: 'Generation prompt',
      },
      {
        name: 'negative_prompt',
        type: 'string',
        required: false,
        default: '',
        description: 'Negative prompt',
      },
      {
        name: 'width',
        type: 'number',
        required: false,
        default: 1024,
        min: 512,
        max: 2048,
        description: 'Image width',
      },
      {
        name: 'height',
        type: 'number',
        required: false,
        default: 1024,
        min: 512,
        max: 2048,
        description: 'Image height',
      },
      {
        name: 'steps',
        type: 'number',
        required: false,
        default: 30,
        min: 1,
        max: 150,
        description: 'Sampling steps',
      },
      {
        name: 'cfg',
        type: 'number',
        required: false,
        default: 7.5,
        min: 1,
        max: 30,
        description: 'CFG scale',
      },
      {
        name: 'seed',
        type: 'number',
        required: false,
        default: -1,
        description: 'Random seed (-1 for random)',
      },
    ],
    workflow: {
      /* ... */
    },
    requiredModels: ['sd_xl_base_1.0.safetensors'],
    requiredNodes: [],
    estimatedTime: 30,
    estimatedVram: 8,
  },
  // ... more templates
];

function getTemplateById(id: string): WorkflowTemplate | undefined {
  return WORKFLOW_TEMPLATES.find((t) => t.id === id);
}

function instantiateTemplate(
  templateId: string,
  params: Record<string, unknown>
): ComfyWorkflow {
  const template = getTemplateById(templateId);
  if (!template) {
    throw new Error(`Template not found: ${templateId}`);
  }

  // Validate required parameters
  for (const param of template.parameters) {
    if (param.required && !(param.name in params)) {
      throw new Error(`Missing required parameter: ${param.name}`);
    }
  }

  // Apply defaults
  const finalParams: Record<string, unknown> = {};
  for (const param of template.parameters) {
    finalParams[param.name] = params[param.name] ?? param.default;
  }

  return injectParameters(template.workflow, finalParams);
}
```

---

## Best Practices

### 1. Use Deterministic Seeds

```typescript
// For reproducibility, always set explicit seeds
const seed =
  params.seed === -1 ? Math.floor(Math.random() * 2147483647) : params.seed;

// Store the seed with the result for reproducibility
const result = await provider.submitJob({
  workflow,
  metadata: { seed },
});
```

### 2. Optimize Node Order

Place nodes in execution order to help visualization and debugging:

```typescript
// Good: Sequential IDs match execution order
const workflow = {
  nodes: {
    '1': { class_type: 'CheckpointLoaderSimple', ... },  // Load first
    '2': { class_type: 'CLIPTextEncode', ... },          // Then encode
    '3': { class_type: 'EmptyLatentImage', ... },        // Create latent
    '4': { class_type: 'KSampler', ... },                // Then sample
    '5': { class_type: 'VAEDecode', ... },               // Decode
    '6': { class_type: 'SaveImage', ... },               // Finally save
  },
};
```

### 3. Validate Before Submission

```typescript
async function submitWorkflow(workflow: ComfyWorkflow): Promise<JobResult> {
  // Validate structure
  const validation = validateWorkflowStructure(workflow);
  if (!validation.valid) {
    throw new Error(`Invalid workflow: ${JSON.stringify(validation.errors)}`);
  }

  // Check for cycles
  const cycles = detectCycles(workflow);
  if (cycles.length > 0) {
    throw new Error(`Workflow contains cycles: ${JSON.stringify(cycles)}`);
  }

  // Log warnings
  for (const warning of validation.warnings) {
    console.warn(`Workflow warning: ${warning.message}`);
  }

  return provider.submitJob({ workflow });
}
```

### 4. Handle Large Workflows

```typescript
// Split large workflows into stages
async function executeMultiStage(
  stages: ComfyWorkflow[]
): Promise<JobOutput[]> {
  const outputs: JobOutput[] = [];

  for (const [index, workflow] of stages.entries()) {
    console.log(`Executing stage ${index + 1}/${stages.length}`);

    const result = await provider.submitJob({ workflow });
    outputs.push(...result.outputs);

    // Pass outputs to next stage if needed
    if (index < stages.length - 1) {
      // Upload intermediate results for next stage
    }
  }

  return outputs;
}
```

### 5. Cache Workflow Templates

```typescript
const workflowCache = new Map<string, ComfyWorkflow>();

function getCachedWorkflow(templateId: string): ComfyWorkflow {
  if (!workflowCache.has(templateId)) {
    const template = getTemplateById(templateId);
    if (template) {
      workflowCache.set(templateId, structuredClone(template.workflow));
    }
  }

  return structuredClone(workflowCache.get(templateId)!);
}
```

---

## Related Documentation

- [Node Reference](./node-reference.md)
- [Performance Tuning](./performance-tuning.md)
- [Custom Node Development](./custom-nodes.md)
- [RunComfy API Client](../../domains/lilith/extras/comfyui/runcomfy-api-client.md)
