Back to Lobehub

ComfyUI 扩展开发指南

docs/development/basic/comfyui-development.zh-CN.mdx

2.1.5627.4 KB
Original Source

ComfyUI 扩展开发指南

本指南基于实际代码实现,帮助开发者扩展 LobeHub 的 ComfyUI 集成功能。

架构概览

LobeHub ComfyUI 集成采用四层服务架构,围绕 LobeComfyUI 主类构建:

plaintext
packages/model-runtime/src/providers/comfyui/
├── index.ts                      # LobeComfyUI 主类入口
├── services/                     # 四大核心服务
│   ├── comfyuiClient.ts         # ComfyUIClientService - 客户端和认证
│   ├── modelResolver.ts         # ModelResolverService - 模型解析
│   ├── workflowBuilder.ts       # WorkflowBuilderService - 工作流构建
│   └── imageService.ts          # ImageService - 图像生成
├── config/                       # 配置系统
│   ├── modelRegistry.ts         # 主模型注册表(222个模型)
│   ├── fluxModelRegistry.ts     # 130个FLUX模型配置
│   ├── sdModelRegistry.ts       # 92个SD系列模型配置
│   ├── systemComponents.ts      # VAE/CLIP/T5/LoRA/ControlNet组件
│   └── workflowRegistry.ts      # 工作流路由配置
├── workflows/                    # 工作流实现
│   ├── flux-dev.ts              # FLUX Dev 20步工作流
│   ├── flux-schnell.ts          # FLUX Schnell 4步快速工作流
│   ├── flux-kontext.ts          # FLUX Kontext 填充工作流
│   ├── sd35.ts                  # SD3.5 外部编码器工作流
│   ├── simple-sd.ts             # 通用SD工作流
│   └── index.ts                 # 工作流导出
├── utils/                        # 工具层
│   ├── staticModelLookup.ts     # 模型查找函数
│   ├── workflowDetector.ts      # 模型架构检测
│   ├── promptSplitter.ts        # FLUX双提示词分割
│   ├── seedGenerator.ts         # 随机种子生成
│   ├── cacheManager.ts          # TTL缓存管理
│   └── workflowUtils.ts         # 工作流工具函数
└── errors/                       # 错误处理
    ├── base.ts                  # 基础错误类
    ├── modelResolverError.ts    # 模型解析错误
    ├── workflowError.ts         # 工作流错误
    └── servicesError.ts         # 服务错误

src/server/services/comfyui/     # 服务端实现
├── core/                         # 核心服务器服务
│   ├── comfyUIAuthService.ts    # 认证服务
│   ├── comfyUIClientService.ts  # 客户端服务
│   ├── comfyUIConnectionService.ts # 连接服务
│   ├── errorHandlerService.ts   # 错误处理服务
│   ├── imageService.ts          # 图像生成服务
│   ├── modelResolverService.ts  # 模型解析服务
│   └── workflowBuilderService.ts # 工作流构建服务
├── config/                       # 服务器端配置
│   ├── constants.ts             # 常量和默认值
│   ├── modelRegistry.ts         # 模型注册表
│   ├── fluxModelRegistry.ts     # FLUX模型
│   ├── sdModelRegistry.ts       # SD模型
│   ├── systemComponents.ts      # 系统组件
│   └── workflowRegistry.ts      # 工作流注册表
├── workflows/                    # 服务端工作流实现
│   ├── flux-dev.ts              # FLUX Dev 工作流
│   ├── flux-schnell.ts          # FLUX Schnell 工作流
│   ├── flux-kontext.ts          # FLUX Kontext 工作流
│   ├── sd35.ts                  # SD3.5 工作流
│   └── simple-sd.ts             # Simple SD 工作流
├── utils/                        # 服务器工具
│   ├── cacheManager.ts          # 缓存管理
│   ├── componentInfo.ts         # 组件信息
│   ├── imageResizer.ts          # 图像调整
│   ├── promptSplitter.ts        # 提示词分割
│   ├── staticModelLookup.ts     # 模型查找
│   ├── weightDType.ts           # 权重数据类型工具
│   ├── workflowDetector.ts      # 工作流检测
│   └── workflowUtils.ts         # 工作流工具
└── errors/                       # 服务器错误处理
    ├── base.ts                  # 基础错误类
    ├── configError.ts           # 配置错误
    ├── modelResolverError.ts    # 模型解析器错误
    ├── servicesError.ts         # 服务错误
    ├── utilsError.ts            # 工具错误
    └── workflowError.ts         # 工作流错误

packages/model-runtime/src/utils/  # 共享工具
└── comfyuiErrorParser.ts        # 客户端/服务器统一错误解析器

核心服务架构

LobeComfyUI 主类初始化四个核心服务:

typescript
// packages/model-runtime/src/providers/comfyui/index.ts
export class LobeComfyUI implements LobeRuntimeAI, AuthenticatedImageRuntime {
  constructor(options: ComfyUIKeyVault = {}) {
    // 1. 客户端服务 - 处理认证和API调用
    this.clientService = new ComfyUIClientService(options);

    // 2. 模型解析服务 - 模型查找和组件选择
    const modelResolverService = new ModelResolverService(this.clientService);

    // 3. 工作流构建服务 - 路由和构建工作流
    const workflowBuilderService = new WorkflowBuilderService({
      clientService: this.clientService,
      modelResolverService: modelResolverService,
    });

    // 4. 图像服务 - 统一的图像生成入口
    this.imageService = new ImageService(
      this.clientService,
      modelResolverService,
      workflowBuilderService,
    );
  }
}

认证系统

ComfyUI 集成支持四种认证方式,由 ComfyUIClientService 内的 AuthManager 处理:

支持的认证类型

typescript
interface ComfyUIKeyVault {
  baseURL: string;
  authType?: 'none' | 'basic' | 'bearer' | 'custom';
  // Basic Auth
  username?: string;
  password?: string;
  // Bearer Token
  apiKey?: string;
  // Custom Headers
  customHeaders?: Record<string, string>;
}

认证配置示例

typescript
// 无认证
const comfyUI = new LobeComfyUI({
  baseURL: 'http://localhost:8000',
  authType: 'none'
});

// 基础认证
const comfyUI = new LobeComfyUI({
  baseURL: 'https://your-comfyui-server.com',
  authType: 'basic',
  username: 'your-username',
  password: 'your-password'
});

// Bearer Token
const comfyUI = new LobeComfyUI({
  baseURL: 'https://your-comfyui-server.com',
  authType: 'bearer',
  apiKey: 'your-api-key'
});

// 自定义头部
const comfyUI = new LobeComfyUI({
  baseURL: 'https://your-comfyui-server.com',
  authType: 'custom',
  customHeaders: {
    'X-API-Key': 'your-custom-key',
    'Authorization': 'Custom your-token'
  }
});

WebAPI 路由

ComfyUI 提供了用于图像生成的 REST WebAPI 路由,支持常规认证和内部服务认证:

路由详情

typescript
// src/app/(backend)/webapi/create-image/comfyui/route.ts
export const runtime = 'nodejs';
export const maxDuration = 300;  // 最长5分钟

// POST /api/create-image/comfyui
{
  model: string;           // 模型标识符
  params: {                // 生成参数
    prompt: string;
    width?: number;
    height?: number;
    // ... 其他参数
  };
  options?: {              // 可选生成选项
    // ... 额外选项
  };
}

认证中间件

WebAPI 路由使用 checkAuth 中间件进行认证:

typescript
import { checkAuth } from '@/app/(backend)/middleware/auth';

// 路由自动验证 JWT 令牌
// 并将认证上下文传递给 tRPC 调用器

错误处理

WebAPI 路由提供结构化的错误响应:

typescript
// 从 TRPCError 的 cause 中提取 AgentRuntimeError
if (agentError && 'errorType' in agentError) {
  // 将 errorType 转换为适当的 HTTP 状态码
  // 401 对应 InvalidProviderAPIKey
  // 403 对应 PermissionDenied
  // 404 对应 NotFound
  // 500+ 对应服务器错误
}

添加新模型

1. 理解模型注册表结构

模型配置存储在配置文件中:

typescript
// packages/model-runtime/src/providers/comfyui/config/modelRegistry.ts
export interface ModelConfig {
  modelFamily: 'FLUX' | 'SD1' | 'SDXL' | 'SD3';
  priority: number;        // 1=官方, 2=企业, 3=社区
  recommendedDtype?: 'default' | 'fp8_e4m3fn' | 'fp8_e5m2';
  variant: string;         // 模型变体标识符
}

2. 添加 FLUX 模型

fluxModelRegistry.ts 中添加新模型:

typescript
// packages/model-runtime/src/providers/comfyui/config/fluxModelRegistry.ts
export const FLUX_MODEL_REGISTRY: Record<string, ModelConfig> = {
  // 现有模型...

  // 添加新的FLUX Dev模型
  'your-custom-flux-dev.safetensors': {
    modelFamily: 'FLUX',
    priority: 2,  // 企业级模型
    variant: 'dev',
    recommendedDtype: 'default',
  },

  // 添加量化版本
  'your-custom-flux-dev-fp8.safetensors': {
    modelFamily: 'FLUX',
    priority: 2,
    variant: 'dev',
    recommendedDtype: 'fp8_e4m3fn',
  },
};

3. 添加 SD 系列模型

sdModelRegistry.ts 中添加:

typescript
// packages/model-runtime/src/providers/comfyui/config/sdModelRegistry.ts
export const SD_MODEL_REGISTRY: Record<string, ModelConfig> = {
  // 现有模型...

  // 添加新的SD3.5模型
  'your-custom-sd35.safetensors': {
    modelFamily: 'SD3',
    priority: 2,
    variant: 'sd35',
    recommendedDtype: 'default',
  },
};

4. 更新模型 ID 映射(可选)

如果需要为前端提供友好的模型 ID,在 modelRegistry.ts 中添加映射:

typescript
// packages/model-runtime/src/providers/comfyui/config/modelRegistry.ts
export const MODEL_ID_VARIANT_MAP: Record<string, string> = {
  // 现有映射...

  // 添加新模型的友好ID
  'my-custom-flux': 'dev',  // 映射到dev变体
  'my-custom-sd35': 'sd35', // 映射到sd35变体
};

创建新工作流

工作流创建原理

重要:工作流节点结构来自 ComfyUI 原生导出

  1. 在 ComfyUI 界面中设计工作流
  2. 使用 "Export (API Format)" 导出 JSON
  3. 将 JSON 结构复制到 TypeScript 文件
  4. 使用PromptBuilder包装并参数化

1. 从 ComfyUI 导出工作流

在 ComfyUI 界面中:

  1. 拖拽节点构建所需工作流
  2. 连接各节点的输入输出
  3. 右键点击空白处 → "Export (API Format)"
  4. 复制生成的 JSON 结构

2. 工作流文件模板

创建新文件 workflows/your-workflow.ts

typescript
import { PromptBuilder } from '@saintno/comfyui-sdk';

import type { WorkflowContext } from '../services/workflowBuilder';
import { generateUniqueSeeds } from '../utils/seedGenerator';
import { getWorkflowFilenamePrefix } from '../utils/workflowUtils';

/**
 * 构建自定义工作流
 * @param modelFileName - 模型文件名
 * @param params - 生成参数
 * @param context - 工作流上下文
 */
export async function buildYourCustomWorkflow(
  modelFileName: string,
  params: Record<string, any>,
  context: WorkflowContext,
): Promise<PromptBuilder<any, any, any>> {

  // 从ComfyUI "Export (API Format)" 获得的JSON结构
  const workflow = {
    '1': {
      _meta: { title: 'Load Checkpoint' },
      class_type: 'CheckpointLoaderSimple',
      inputs: {
        ckpt_name: modelFileName,
      },
    },
    '2': {
      _meta: { title: 'CLIP Text Encode' },
      class_type: 'CLIPTextEncode',
      inputs: {
        clip: ['1', 1],  // 连接到节点1的CLIP输出
        text: params.prompt,
      },
    },
    '3': {
      _meta: { title: 'Empty Latent' },
      class_type: 'EmptyLatentImage',
      inputs: {
        width: params.width,
        height: params.height,
        batch_size: 1,
      },
    },
    '4': {
      _meta: { title: 'KSampler' },
      class_type: 'KSampler',
      inputs: {
        model: ['1', 0],      // 连接到节点1的MODEL输出
        positive: ['2', 0],    // 连接到节点2的CONDITIONING输出
        negative: ['2', 0],    // 可以配置负面提示词
        latent_image: ['3', 0],
        seed: params.seed ?? generateUniqueSeeds(1)[0],
        steps: params.steps,
        cfg: params.cfg,
        sampler_name: 'euler',
        scheduler: 'normal',
        denoise: 1.0,
      },
    },
    '5': {
      _meta: { title: 'VAE Decode' },
      class_type: 'VAEDecode',
      inputs: {
        samples: ['4', 0],
        vae: ['1', 2],        // 连接到节点1的VAE输出
      },
    },
    '6': {
      _meta: { title: 'Save Image' },
      class_type: 'SaveImage',
      inputs: {
        filename_prefix: getWorkflowFilenamePrefix('buildYourCustomWorkflow', context.variant),
        images: ['5', 0],
      },
    },
  };

  // 使用PromptBuilder包装静态JSON
  const builder = new PromptBuilder(
    workflow,
    ['width', 'height', 'steps', 'cfg', 'seed'],  // 输入参数
    ['images'],  // 输出参数
  );

  // 设置输出节点
  builder.setOutputNode('images', '6');

  // 设置输入节点路径
  builder.setInputNode('width', '3.inputs.width');
  builder.setInputNode('height', '3.inputs.height');
  builder.setInputNode('steps', '4.inputs.steps');
  builder.setInputNode('cfg', '4.inputs.cfg');
  builder.setInputNode('seed', '4.inputs.seed');

  // 设置参数值
  builder
    .input('width', params.width)
    .input('height', params.height)
    .input('steps', params.steps)
    .input('cfg', params.cfg)
    .input('seed', params.seed ?? generateUniqueSeeds(1)[0]);

  return builder;
}

3. 注册新工作流

workflowRegistry.ts 中添加工作流映射:

typescript
// packages/model-runtime/src/providers/comfyui/config/workflowRegistry.ts
import { buildYourCustomWorkflow } from '../workflows/your-workflow';

export const VARIANT_WORKFLOW_MAP: Record<string, WorkflowBuilder> = {
  // 现有映射...

  // 添加新工作流
  'your-variant': buildYourCustomWorkflow,
};

4. 实际工作流示例

参考 flux-dev.ts 的真实实现:

typescript
// packages/model-runtime/src/providers/comfyui/workflows/flux-dev.ts (简化版)
export async function buildFluxDevWorkflow(
  modelFileName: string,
  params: Record<string, any>,
  context: WorkflowContext,
): Promise<PromptBuilder<any, any, any>> {
  // 获取所需组件
  const selectedT5Model = await context.modelResolverService.getOptimalComponent('t5', 'FLUX');
  const selectedVAE = await context.modelResolverService.getOptimalComponent('vae', 'FLUX');
  const selectedCLIP = await context.modelResolverService.getOptimalComponent('clip', 'FLUX');

  // 处理双提示词分割
  const { t5xxlPrompt, clipLPrompt } = splitPromptForDualCLIP(params.prompt);

  // 静态工作流定义(来自ComfyUI导出)
  const workflow = {
    '1': {
      class_type: 'DualCLIPLoader',
      inputs: {
        clip_name1: selectedT5Model,
        clip_name2: selectedCLIP,
        type: 'flux',
      },
    },
    // ... 更多节点
  };

  // 参数注入(必须在workflow文件内完成)
  workflow['5'].inputs.clip_l = clipLPrompt;
  workflow['5'].inputs.t5xxl = t5xxlPrompt;
  workflow['4'].inputs.width = params.width;
  workflow['4'].inputs.height = params.height;

  // 创建并配置PromptBuilder
  const builder = new PromptBuilder(workflow, inputs, outputs);
  // 配置输入输出映射...

  return builder;
}

系统组件管理

组件配置结构

所有系统组件(VAE、CLIP、T5、LoRA、ControlNet)统一配置在 systemComponents.ts

typescript
// packages/model-runtime/src/providers/comfyui/config/systemComponents.ts
export interface ComponentConfig {
  modelFamily: string;           // 模型家族
  priority: number;             // 1=必需, 2=标准, 3=可选
  type: string;                 // 组件类型
  compatibleVariants?: string[]; // 兼容变体(LoRA/ControlNet)
  controlnetType?: string;      // ControlNet类型
}

export const SYSTEM_COMPONENTS: Record<string, ComponentConfig> = {
  // VAE组件
  'ae.safetensors': {
    modelFamily: 'FLUX',
    priority: 1,
    type: 'vae',
  },

  // CLIP组件
  'clip_l.safetensors': {
    modelFamily: 'FLUX',
    priority: 1,
    type: 'clip',
  },

  // T5编码器
  't5xxl_fp16.safetensors': {
    modelFamily: 'FLUX',
    priority: 1,
    type: 't5',
  },

  // LoRA适配器
  'realism_lora.safetensors': {
    compatibleVariants: ['dev'],
    modelFamily: 'FLUX',
    priority: 1,
    type: 'lora',
  },

  // ControlNet模型
  'flux-controlnet-canny-v3.safetensors': {
    compatibleVariants: ['dev'],
    controlnetType: 'canny',
    modelFamily: 'FLUX',
    priority: 1,
    type: 'controlnet',
  },
};

添加新组件

typescript
// 添加新的LoRA
'your-custom-lora.safetensors': {
  compatibleVariants: ['dev', 'schnell'],
  modelFamily: 'FLUX',
  priority: 2,
  type: 'lora',
},

// 添加新的ControlNet
'your-controlnet-pose.safetensors': {
  compatibleVariants: ['dev'],
  controlnetType: 'pose',
  modelFamily: 'FLUX',
  priority: 2,
  type: 'controlnet',
},

组件查询 API

typescript
import { getAllComponentsWithNames, getOptimalComponent } from '../config/systemComponents';

// 获取最优组件
const bestVAE = getOptimalComponent('vae', 'FLUX');
const bestT5 = getOptimalComponent('t5', 'FLUX');

// 查询特定类型的组件
const availableLoras = getAllComponentsWithNames({
  type: 'lora',
  modelFamily: 'FLUX',
  compatibleVariant: 'dev'
});

// 查询ControlNet
const cannyControlNets = getAllComponentsWithNames({
  type: 'controlnet',
  controlnetType: 'canny',
  modelFamily: 'FLUX'
});

模型解析和查找

ModelResolverService 工作原理

typescript
// packages/model-runtime/src/providers/comfyui/services/modelResolver.ts
export class ModelResolverService {
  async resolveModelFileName(modelId: string): Promise<string | undefined> {
    // 1. 清理模型ID
    const cleanId = modelId.replace(/^comfyui\//, '');

    // 2. 检查模型ID映射
    const mappedVariant = MODEL_ID_VARIANT_MAP[cleanId];
    if (mappedVariant) {
      const prioritizedModels = getModelsByVariant(mappedVariant);
      const serverModels = await this.getAvailableModelFiles();

      // 按优先级查找第一个可用模型
      for (const filename of prioritizedModels) {
        if (serverModels.includes(filename)) {
          return filename;
        }
      }
    }

    // 3. 直接注册表查找
    if (MODEL_REGISTRY[cleanId]) {
      return cleanId;
    }

    // 4. 检查服务器文件存在性
    if (isModelFile(cleanId)) {
      const serverModels = await this.getAvailableModelFiles();
      if (serverModels.includes(cleanId)) {
        return cleanId;
      }
    }

    return undefined;
  }
}

模型查找示例

typescript
// 实际使用示例
const resolver = new ModelResolverService(clientService);

// 友好ID查找
const fluxDevFile = await resolver.resolveModelFileName('flux-dev');
// 返回: 'flux1-dev.safetensors' (如果存在)

// 直接文件名查找
const directFile = await resolver.resolveModelFileName('my-custom-model.safetensors');
// 返回: 'my-custom-model.safetensors' (如果存在)

// 变体查找
const devModels = getModelsByVariant('dev');
console.log(devModels.slice(0, 3));
// 输出: ['flux1-dev.safetensors', 'flux1-dev-fp8.safetensors', ...]

错误处理

错误类型层次

plaintext
// packages/model-runtime/src/providers/comfyui/errors/
ComfyUIInternalError              // 基础错误
├── ModelResolverError           // 模型解析错误
├── WorkflowError               // 工作流错误
├── ServicesError               // 服务错误
└── UtilsError                  // 工具错误

错误处理示例

typescript
import { ModelResolverError, WorkflowError } from '../errors';

try {
  const result = await comfyUI.createImage({
    model: 'nonexistent-model',
    params: { prompt: '测试' }
  });
} catch (error) {
  if (error instanceof ModelResolverError) {
    console.log('模型解析失败:', error.message);
    console.log('错误原因:', error.reason);
    console.log('错误详情:', error.details);
  } else if (error instanceof WorkflowError) {
    console.log('工作流错误:', error.message);
  }
}

统一错误解析器

客户端和服务器端错误处理可使用共享的错误解析器:

typescript
// packages/model-runtime/src/utils/comfyuiErrorParser.ts
import { parseComfyUIErrorMessage, cleanComfyUIErrorMessage } from '../utils/comfyuiErrorParser';

// 解析错误消息并确定错误类型
const { error, errorType } = parseComfyUIErrorMessage(rawError);

// 清理 ComfyUI 格式的错误消息
const cleanMessage = cleanComfyUIErrorMessage(errorMessage);

错误解析器处理:

  • HTTP 状态码映射到错误类型
  • 服务器端错误增强
  • 模型文件缺失检测
  • 网络错误识别
  • 工作流验证错误

测试架构与开发

测试架构概述

ComfyUI 集成使用了统一的测试架构,确保测试的可维护性和定制友好性。该架构包括:

  • 统一 Mock 系统:集中管理所有外部依赖的模拟
  • 参数化测试:自动适应新模型,无需修改现有测试
  • 夹具系统:从配置文件中获取测试数据,确保准确性
  • 覆盖率目标:ComfyUI 模块维持 97%+ 覆盖率

测试文件结构

plaintext
packages/model-runtime/src/providers/comfyui/__tests__/
├── setup/
│   └── unifiedMocks.ts              # 统一Mock配置
├── fixtures/
│   ├── parameters.fixture.ts        # 参数测试夹具
│   └── workflow.fixture.ts          # 工作流测试夹具
├── integration/
│   ├── parameterMapping.test.ts     # 参数映射集成测试
│   └── workflowBuilder.test.ts      # 工作流构建测试
├── services/                        # 各服务单元测试
└── workflows/                       # 工作流单元测试

添加新模型测试

当添加新模型时,测试会自动识别并运行相应的参数映射测试。你只需要:

1. 在模型配置中添加参数架构

typescript
// packages/model-bank/src/aiModels/comfyui.ts
export const myNewModelParamsSchema = {
  prompt: { type: 'string', required: true },
  steps: { type: 'number', default: 20, min: 1, max: 150 },
  cfg: { type: 'number', default: 7.0, min: 1.0, max: 30.0 }
};

2. 创建工作流构建器

typescript
// packages/model-runtime/src/providers/comfyui/workflows/myNewModel.ts
export async function buildMyNewModelWorkflow(
  modelName: string,
  params: MyNewModelParams,
  context: ComfyUIContext
) {
  const workflow = { /* 工作流定义 */ };

  // 参数注入
  workflow['1'].inputs.prompt = params.prompt;
  workflow['2'].inputs.steps = params.steps;

  return workflow;
}

3. 在夹具中注册模型

typescript
// packages/model-runtime/src/providers/comfyui/__tests__/fixtures/parameters.fixture.ts
import {
  myNewModelParamsSchema,
  // ... 其他架构
} from '../../../../../model-bank/src/aiModels/comfyui';

export const parametersFixture = {
  models: {
    'my-new-model': {
      schema: myNewModelParamsSchema,
      defaults: {
        steps: myNewModelParamsSchema.steps.default,
        cfg: myNewModelParamsSchema.cfg.default,
      },
      boundaries: {
        min: { steps: myNewModelParamsSchema.steps.min },
        max: { steps: myNewModelParamsSchema.steps.max }
      }
    }
  }
};

测试最佳实践

使用统一 Mock 系统

typescript
import { setupAllMocks } from '../setup/unifiedMocks';

describe('MyTest', () => {
  const mocks = setupAllMocks();

  beforeEach(() => {
    vi.clearAllMocks();
  });
});

编写参数映射测试

参数映射测试会自动运行,验证前端参数正确注入到工作流中:

typescript
// 测试会自动包含新注册的模型
describe.each(
  Object.entries(models).filter(([name]) => workflowBuilders[name])
)(
  '%s parameter mapping',
  (modelName, modelConfig) => {
    it('should map schema parameters to workflow', async () => {
      const params = {
        prompt: 'test prompt',
        ...modelConfig.defaults,
      };

      const workflow = await builder(`${modelName}.safetensors`, params, mockContext);
      expect(workflow).toBeDefined();
    });
  }
);

定制友好的测试原则

  • 不测试工作流结构:工作流是 ComfyUI 官方格式,只测试参数映射
  • 使用配置驱动的数据:测试数据来自模型配置文件,确保一致性
  • 避免脆性断言:不检查具体的节点 ID 或内部结构
  • 支持扩展:新增模型应该只影响覆盖率,不破坏现有测试

运行测试

bash
# 运行 ComfyUI 相关测试
cd packages/model-runtime
bunx vitest run --silent='passed-only' 'src/comfyui'

# 查看覆盖率
bunx vitest run --coverage 'src/comfyui'

# 运行特定测试文件
bunx vitest run 'src/comfyui/__tests__/integration/parameterMapping.test.ts'

覆盖率目标

  • 整体覆盖率:ComfyUI 模块维持 97%+ 覆盖率
  • 核心功能:100% 分支覆盖率
  • 新增功能:保持或提升现有覆盖率水平

开发和测试

1. 本地开发设置

bash
# 启动ComfyUI调试模式
DEBUG=lobe-image:* pnpm dev

2. 测试新功能

typescript
// 创建测试文件
import { buildYourCustomWorkflow } from './your-workflow';

describe('Custom Workflow', () => {
  test('should build workflow correctly', async () => {
    const mockContext = {
      clientService: mockClientService,
      modelResolverService: mockModelResolver,
    };

    const workflow = await buildYourCustomWorkflow(
      'test-model.safetensors',
      { prompt: '测试', width: 512, height: 512 },
      mockContext
    );

    expect(workflow).toBeDefined();
    // 验证工作流结构...
  });
});

3. 模型配置测试

typescript
import { getModelConfig, getAllModelNames } from '../config/modelRegistry';

describe('Model Registry', () => {
  test('should find new model', () => {
    const config = getModelConfig('your-new-model.safetensors');
    expect(config).toBeDefined();
    expect(config?.variant).toBe('dev');
    expect(config?.modelFamily).toBe('FLUX');
  });
});

完整使用示例

基础图像生成

typescript
import { LobeComfyUI } from '@/libs/model-runtime/comfyui';

const comfyUI = new LobeComfyUI({
  baseURL: 'http://localhost:8000',
  authType: 'none'
});

// FLUX Dev模型生成
const result = await comfyUI.createImage({
  model: 'flux-dev',
  params: {
    prompt: '美丽的风景画,高质量,详细',
    width: 1024,
    height: 1024,
    steps: 20,
    cfg: 3.5,
    seed: -1
  }
});

console.log('生成图像URL:', result.imageUrl);

SD3.5 模型使用

typescript
// SD3.5会自动检测可用编码器
const sd35Result = await comfyUI.createImage({
  model: 'stable-diffusion-35',
  params: {
    prompt: '未来主义城市景观',
    width: 1344,
    height: 768,
    steps: 28,
    cfg: 4.5
  }
});

企业优化模型

typescript
// 系统会自动选择最佳可用变体(如FP8量化版本)
const optimizedResult = await comfyUI.createImage({
  model: 'flux-dev',
  params: {
    prompt: '专业商务肖像',
    width: 768,
    height: 1024,
    steps: 15  // FP8模型可以用更少步数
  }
});

注意事项

  • 确保 ComfyUI 服务正常运行并可访问
  • 检查所有必需的模型文件是否已正确安装
  • 注意模型文件的命名规范和路径配置
  • 定期检查和更新工作流配置以支持新功能
  • 注意不同模型系列的参数差异和兼容性
  • 添加新模型时,请遵循测试架构指南确保测试完整性
  • 在提交代码前务必运行相关测试确保覆盖率达标

通过遵循这些指南,开发者可以有效地在 LobeHub 中使用和扩展 ComfyUI 功能,为用户提供强大的图像生成和处理能力。