bw-mini-app-server/docs/template-management-system.md

48 KiB
Raw Blame History

AI模板管理系统设计方案 (面向对象版本)

1. 系统概述

1.1 核心目标

  • 统一接口: 通过抽象类抹平不同AI模型和API的调用差异
  • 模板化管理: 将复杂的AI调用封装为简单的模板类
  • 类型安全: 利用TypeScript的类型系统确保参数和返回值的正确性
  • 易于扩展: 通过继承轻松添加新的模板类型

1.2 设计优势

  • 编译时检查: TypeScript编译时就能发现接口不匹配问题
  • 代码复用: 通过继承实现通用逻辑的复用
  • 类型推导: IDE能提供完整的代码提示和类型检查
  • 易于测试: 每个模板类都可以独立进行单元测试

1.3 模板类型示例

  • 换装模板: 图片 + 服装描述 → 换装后图片
  • 抠图模板: 图片 + 物体描述 → 抠图视频
  • 风格转换: 图片 + 风格描述 → 风格化图片
  • 背景替换: 图片 + 背景描述 → 新背景图片

2. 核心抽象类设计

2.1 基础模板抽象类

// src/templates/base/template.abstract.ts
export interface TemplateMetadata {
  code: string;
  name: string;
  description: string;
  category: string;
  creditCost: number;
  version: string;
  previewImageUrl?: string;
}

export interface TemplateExecutionContext {
  userId: string;
  platform: string;
  requestId: string;
  timestamp: Date;
}

export interface TemplateExecutionResult<T = any> {
  success: boolean;
  data?: T;
  error?: string;
  metadata: {
    executionTime: number;
    creditCost: number;
    templateCode: string;
    aiProvider?: string;
  };
}

export abstract class Template<TInput = any, TOutput = any> {
  // 模板元数据
  abstract readonly metadata: TemplateMetadata;

  // 模板是否启用
  protected _enabled: boolean = true;

  constructor() {
    this.validateTemplate();
  }

  // 获取模板信息
  getMetadata(): TemplateMetadata {
    return { ...this.metadata };
  }

  // 检查模板是否启用
  isEnabled(): boolean {
    return this._enabled;
  }

  // 启用/禁用模板
  setEnabled(enabled: boolean): void {
    this._enabled = enabled;
  }

  // 验证输入参数 - 子类必须实现
  abstract validateInput(input: TInput): Promise<string[]>;

  // 执行模板 - 子类必须实现
  abstract execute(input: TInput, context: TemplateExecutionContext): Promise<TOutput>;

  // 获取参数定义 - 用于前端生成表单
  abstract getParameterDefinitions(): ParameterDefinition[];

  // 模板执行入口 - 统一的执行流程
  async run(input: TInput, context: TemplateExecutionContext): Promise<TemplateExecutionResult<TOutput>> {
    const startTime = Date.now();

    try {
      // 检查模板是否启用
      if (!this.isEnabled()) {
        throw new Error(`模板 ${this.metadata.code} 已禁用`);
      }

      // 验证输入参数
      const validationErrors = await this.validateInput(input);
      if (validationErrors.length > 0) {
        throw new Error(`参数验证失败: ${validationErrors.join(', ')}`);
      }

      // 执行模板
      const result = await this.execute(input, context);
      const executionTime = Date.now() - startTime;

      return {
        success: true,
        data: result,
        metadata: {
          executionTime,
          creditCost: this.metadata.creditCost,
          templateCode: this.metadata.code,
        }
      };

    } catch (error) {
      const executionTime = Date.now() - startTime;

      return {
        success: false,
        error: error.message,
        metadata: {
          executionTime,
          creditCost: 0, // 失败不扣积分
          templateCode: this.metadata.code,
        }
      };
    }
  }

  // 验证模板定义的完整性
  private validateTemplate(): void {
    if (!this.metadata.code) {
      throw new Error('模板代码不能为空');
    }
    if (!this.metadata.name) {
      throw new Error('模板名称不能为空');
    }
    if (this.metadata.creditCost < 0) {
      throw new Error('积分消耗不能为负数');
    }
  }
}

2.2 参数定义接口

// src/templates/base/parameter.interface.ts
export enum ParameterType {
  STRING = 'string',
  NUMBER = 'number',
  BOOLEAN = 'boolean',
  IMAGE = 'image',
  SELECT = 'select',
  RANGE = 'range',
  ARRAY = 'array'
}

export interface ParameterValidation {
  required?: boolean;
  min?: number;
  max?: number;
  options?: string[];
  pattern?: string;
  minLength?: number;
  maxLength?: number;
}

export interface ParameterDefinition {
  key: string;
  displayName: string;
  description: string;
  type: ParameterType;
  defaultValue?: any;
  validation?: ParameterValidation;
  order: number;
}

2.3 图片生成模板抽象类

// src/templates/base/image-generate-template.abstract.ts
import { Template, TemplateExecutionContext } from './template.abstract';

export interface ImageGenerateInput {
  prompt: string;
  inputImage?: string; // base64 或 URL
  width?: number;
  height?: number;
  quality?: 'standard' | 'high' | 'ultra';
  style?: string;
  [key: string]: any; // 允许扩展参数
}

export interface ImageGenerateOutput {
  images: Array<{
    url: string;
    thumbnailUrl?: string;
    width: number;
    height: number;
    format: string;
  }>;
  seed?: number;
  parameters?: Record<string, any>;
}

export abstract class ImageGenerateTemplate extends Template<ImageGenerateInput, ImageGenerateOutput> {
  // 通用的图片参数验证
  async validateInput(input: ImageGenerateInput): Promise<string[]> {
    const errors: string[] = [];

    // 验证提示词
    if (!input.prompt || input.prompt.trim().length === 0) {
      errors.push('提示词不能为空');
    }

    if (input.prompt && input.prompt.length > 1000) {
      errors.push('提示词长度不能超过1000个字符');
    }

    // 验证图片尺寸
    if (input.width && (input.width < 256 || input.width > 2048)) {
      errors.push('图片宽度必须在256-2048之间');
    }

    if (input.height && (input.height < 256 || input.height > 2048)) {
      errors.push('图片高度必须在256-2048之间');
    }

    // 验证输入图片
    if (input.inputImage && !this.isValidImageData(input.inputImage)) {
      errors.push('输入图片格式不正确');
    }

    // 调用子类的自定义验证
    const customErrors = await this.validateCustomInput(input);
    errors.push(...customErrors);

    return errors;
  }

  // 子类可以重写此方法添加自定义验证
  protected async validateCustomInput(input: ImageGenerateInput): Promise<string[]> {
    return [];
  }

  // 验证图片数据格式
  protected isValidImageData(imageData: string): boolean {
    // base64格式
    if (imageData.startsWith('data:image/')) {
      return /^data:image\/(jpeg|jpg|png|gif|webp);base64,/.test(imageData);
    }
    // URL格式
    return /^https?:\/\/.+\.(jpeg|jpg|png|gif|webp)$/i.test(imageData);
  }

  // 获取通用的图片生成参数定义
  protected getCommonParameterDefinitions(): ParameterDefinition[] {
    return [
      {
        key: 'prompt',
        displayName: '提示词',
        description: '描述想要生成的图片内容',
        type: ParameterType.STRING,
        validation: { required: true, minLength: 1, maxLength: 1000 },
        order: 1,
      },
      {
        key: 'quality',
        displayName: '生成质量',
        description: '选择生成质量等级',
        type: ParameterType.SELECT,
        defaultValue: 'standard',
        validation: { options: ['standard', 'high', 'ultra'] },
        order: 100,
      }
    ];
  }
}

2.4 视频生成模板抽象类

// src/templates/base/video-generate-template.abstract.ts
import { Template, TemplateExecutionContext } from './template.abstract';

export interface VideoGenerateInput {
  prompt: string;
  inputImage?: string;
  duration?: number; // 秒
  fps?: number;
  resolution?: '720p' | '1080p' | '4k';
  style?: string;
  [key: string]: any;
}

export interface VideoGenerateOutput {
  videoUrl: string;
  thumbnailUrl?: string;
  duration: number;
  fps: number;
  resolution: string;
  format: string;
  fileSize?: number;
}

export abstract class VideoGenerateTemplate extends Template<VideoGenerateInput, VideoGenerateOutput> {
  async validateInput(input: VideoGenerateInput): Promise<string[]> {
    const errors: string[] = [];

    // 验证提示词
    if (!input.prompt || input.prompt.trim().length === 0) {
      errors.push('提示词不能为空');
    }

    // 验证时长
    if (input.duration && (input.duration < 1 || input.duration > 30)) {
      errors.push('视频时长必须在1-30秒之间');
    }

    // 验证帧率
    if (input.fps && (input.fps < 12 || input.fps > 60)) {
      errors.push('帧率必须在12-60之间');
    }

    // 验证输入图片
    if (input.inputImage && !this.isValidImageData(input.inputImage)) {
      errors.push('输入图片格式不正确');
    }

    // 调用子类的自定义验证
    const customErrors = await this.validateCustomInput(input);
    errors.push(...customErrors);

    return errors;
  }

  protected async validateCustomInput(input: VideoGenerateInput): Promise<string[]> {
    return [];
  }

  protected isValidImageData(imageData: string): boolean {
    if (imageData.startsWith('data:image/')) {
      return /^data:image\/(jpeg|jpg|png|gif|webp);base64,/.test(imageData);
    }
    return /^https?:\/\/.+\.(jpeg|jpg|png|gif|webp)$/i.test(imageData);
  }

  protected getCommonParameterDefinitions(): ParameterDefinition[] {
    return [
      {
        key: 'prompt',
        displayName: '提示词',
        description: '描述想要生成的视频内容',
        type: ParameterType.STRING,
        validation: { required: true, minLength: 1, maxLength: 500 },
        order: 1,
      },
      {
        key: 'duration',
        displayName: '视频时长',
        description: '生成视频的时长(秒)',
        type: ParameterType.RANGE,
        defaultValue: 4,
        validation: { min: 1, max: 30 },
        order: 90,
      },
      {
        key: 'resolution',
        displayName: '分辨率',
        description: '选择视频分辨率',
        type: ParameterType.SELECT,
        defaultValue: '720p',
        validation: { options: ['720p', '1080p', '4k'] },
        order: 95,
      }
    ];
  }
}

3. 模板管理器

3.1 模板管理器 (TemplateManager)

// src/services/template-manager.service.ts
import { Injectable, OnModuleInit, Logger } from '@nestjs/common';
import { Template, TemplateExecutionContext, TemplateExecutionResult } from '../templates/base/template.abstract';
import { ParameterDefinition } from '../templates/base/parameter.interface';

export interface TemplateInfo {
  code: string;
  name: string;
  description: string;
  category: string;
  creditCost: number;
  version: string;
  enabled: boolean;
  parameters: ParameterDefinition[];
}

@Injectable()
export class TemplateManager implements OnModuleInit {
  private readonly logger = new Logger(TemplateManager.name);
  private readonly templates = new Map<string, Template>();

  async onModuleInit() {
    // 启动时自动注册所有模板
    await this.registerAllTemplates();
  }

  // 注册模板
  registerTemplate(template: Template): void {
    const code = template.getMetadata().code;

    if (this.templates.has(code)) {
      this.logger.warn(`模板 ${code} 已存在,将被覆盖`);
    }

    this.templates.set(code, template);
    this.logger.log(`模板 ${code} 注册成功`);
  }

  // 批量注册模板
  registerTemplates(templates: Template[]): void {
    templates.forEach(template => this.registerTemplate(template));
  }

  // 卸载模板
  unregisterTemplate(templateCode: string): boolean {
    if (this.templates.has(templateCode)) {
      this.templates.delete(templateCode);
      this.logger.log(`模板 ${templateCode} 已卸载`);
      return true;
    }
    return false;
  }

  // 获取模板
  getTemplate(templateCode: string): Template | null {
    return this.templates.get(templateCode) || null;
  }

  // 获取所有模板信息
  getAllTemplates(): TemplateInfo[] {
    return Array.from(this.templates.values()).map(template => ({
      ...template.getMetadata(),
      enabled: template.isEnabled(),
      parameters: template.getParameterDefinitions(),
    }));
  }

  // 获取启用的模板
  getEnabledTemplates(): TemplateInfo[] {
    return this.getAllTemplates().filter(template => template.enabled);
  }

  // 按分类获取模板
  getTemplatesByCategory(category: string): TemplateInfo[] {
    return this.getAllTemplates().filter(template =>
      template.category === category && template.enabled
    );
  }

  // 搜索模板
  searchTemplates(keyword: string): TemplateInfo[] {
    const lowerKeyword = keyword.toLowerCase();
    return this.getAllTemplates().filter(template =>
      template.enabled && (
        template.name.toLowerCase().includes(lowerKeyword) ||
        template.description.toLowerCase().includes(lowerKeyword) ||
        template.category.toLowerCase().includes(lowerKeyword)
      )
    );
  }

  // 启用/禁用模板
  setTemplateEnabled(templateCode: string, enabled: boolean): boolean {
    const template = this.getTemplate(templateCode);
    if (template) {
      template.setEnabled(enabled);
      this.logger.log(`模板 ${templateCode} ${enabled ? '已启用' : '已禁用'}`);
      return true;
    }
    return false;
  }

  // 执行模板
  async executeTemplate<TInput, TOutput>(
    templateCode: string,
    input: TInput,
    context: TemplateExecutionContext
  ): Promise<TemplateExecutionResult<TOutput>> {
    const template = this.getTemplate(templateCode);

    if (!template) {
      return {
        success: false,
        error: `模板 ${templateCode} 不存在`,
        metadata: {
          executionTime: 0,
          creditCost: 0,
          templateCode,
        }
      };
    }

    this.logger.log(`开始执行模板 ${templateCode},用户: ${context.userId}`);

    const result = await template.run(input, context);

    this.logger.log(
      `模板 ${templateCode} 执行${result.success ? '成功' : '失败'}` +
      `耗时: ${result.metadata.executionTime}ms`
    );

    return result;
  }

  // 获取模板统计信息
  getStatistics() {
    const allTemplates = this.getAllTemplates();
    const enabledTemplates = allTemplates.filter(t => t.enabled);

    const categoryCounts = allTemplates.reduce((acc, template) => {
      acc[template.category] = (acc[template.category] || 0) + 1;
      return acc;
    }, {} as Record<string, number>);

    return {
      total: allTemplates.length,
      enabled: enabledTemplates.length,
      disabled: allTemplates.length - enabledTemplates.length,
      categories: categoryCounts,
    };
  }

  // 自动注册所有模板 - 在实际项目中,这里会导入所有模板类
  private async registerAllTemplates(): Promise<void> {
    // 这里会自动导入和注册所有模板
    // 在实际项目中,可以通过文件扫描或者手动导入的方式
    this.logger.log('开始注册模板...');

    // 示例:手动注册模板(实际项目中可以自动化)
    // const outfitChangeTemplate = new OutfitChangeTemplate();
    // this.registerTemplate(outfitChangeTemplate);

    this.logger.log(`模板注册完成,共注册 ${this.templates.size} 个模板`);
  }
}

3.2 模板工厂 (TemplateFactory)

// src/templates/template.factory.ts
import { Injectable } from '@nestjs/common';
import { Template } from './base/template.abstract';

// 导入所有具体模板类
import { OutfitChangeTemplate } from './image/outfit-change.template';
import { ImageToVideoTemplate } from './video/image-to-video.template';
import { PortraitEnhanceTemplate } from './image/portrait-enhance.template';
import { StyleTransferTemplate } from './image/style-transfer.template';
import { BackgroundReplaceTemplate } from './image/background-replace.template';

@Injectable()
export class TemplateFactory {
  // 创建所有模板实例
  createAllTemplates(): Template[] {
    return [
      new OutfitChangeTemplate(),
      new ImageToVideoTemplate(),
      new PortraitEnhanceTemplate(),
      new StyleTransferTemplate(),
      new BackgroundReplaceTemplate(),
      // 在这里添加新的模板类
    ];
  }

  // 根据代码创建特定模板
  createTemplate(templateCode: string): Template | null {
    const templates = this.createAllTemplates();
    return templates.find(template =>
      template.getMetadata().code === templateCode
    ) || null;
  }

  // 获取所有可用的模板代码
  getAvailableTemplateCodes(): string[] {
    return this.createAllTemplates().map(template =>
      template.getMetadata().code
    );
  }
}

4. 具体模板实现示例

4.1 换装模板实现

// src/templates/image/outfit-change.template.ts
import { Injectable } from '@nestjs/common';
import { ImageGenerateTemplate, ImageGenerateInput, ImageGenerateOutput } from '../base/image-generate-template.abstract';
import { TemplateMetadata, TemplateExecutionContext } from '../base/template.abstract';
import { ParameterDefinition, ParameterType } from '../base/parameter.interface';
import { BowongAIService } from '../../services/bowongai.service';

interface OutfitChangeInput extends ImageGenerateInput {
  inputImage: string; // 必填
  clothingDescription: string;
  style?: 'casual' | 'formal' | 'vintage' | 'modern' | 'elegant';
}

@Injectable()
export class OutfitChangeTemplate extends ImageGenerateTemplate {
  readonly metadata: TemplateMetadata = {
    code: 'outfit_change_v1',
    name: '智能换装',
    description: '上传人物图片描述想要的服装AI自动生成换装效果',
    category: '换装',
    creditCost: 15,
    version: '1.0.0',
    previewImageUrl: '/images/templates/outfit-change-preview.jpg',
  };

  constructor(private readonly bowongAI: BowongAIService) {
    super();
  }

  async execute(input: OutfitChangeInput, context: TemplateExecutionContext): Promise<ImageGenerateOutput> {
    // 构建换装专用提示词
    const prompt = this.buildOutfitChangePrompt(input);

    // 调用BowongAI基础服务传入换装模板的特定参数
    const result = await this.bowongAI.imageToImage({
      prompt,
      imageUrl: input.inputImage,
      model: 'gemini-2.5-flash-image-preview', // 换装模板专用模型
    });

    // 转换为标准输出格式
    return {
      images: result.images.map(img => ({
        url: img.url,
        thumbnailUrl: img.thumbnailUrl,
        width: img.width,
        height: img.height,
        format: 'png',
      })),
      parameters: {
        prompt,
        model: 'gemini-2.5-flash-image-preview',
        workflow: '图生图',
        templateType: 'outfit_change',
      },
    };
  }

  protected async validateCustomInput(input: OutfitChangeInput): Promise<string[]> {
    const errors: string[] = [];

    // 验证必须有输入图片
    if (!input.inputImage) {
      errors.push('必须提供人物图片');
    }

    // 验证服装描述
    if (!input.clothingDescription || input.clothingDescription.trim().length < 3) {
      errors.push('服装描述至少需要3个字符');
    }

    if (input.clothingDescription && input.clothingDescription.length > 200) {
      errors.push('服装描述不能超过200个字符');
    }

    return errors;
  }

  getParameterDefinitions(): ParameterDefinition[] {
    return [
      {
        key: 'inputImage',
        displayName: '人物图片',
        description: '请上传清晰的人物图片建议分辨率1024x1024',
        type: ParameterType.IMAGE,
        validation: { required: true },
        order: 1,
      },
      {
        key: 'clothingDescription',
        displayName: '服装描述',
        description: '详细描述想要的服装样式,如"红色连衣裙"、"黑色西装"等',
        type: ParameterType.STRING,
        validation: { required: true, minLength: 3, maxLength: 200 },
        order: 2,
      },
      {
        key: 'style',
        displayName: '风格',
        description: '选择服装风格',
        type: ParameterType.SELECT,
        defaultValue: 'casual',
        validation: { options: ['casual', 'formal', 'vintage', 'modern', 'elegant'] },
        order: 3,
      },
      ...this.getCommonParameterDefinitions(),
    ];
  }

  private buildOutfitChangePrompt(input: OutfitChangeInput): string {
    const styleMap = {
      casual: 'casual everyday wear',
      formal: 'formal business attire',
      vintage: 'vintage retro style',
      modern: 'modern contemporary fashion',
      elegant: 'elegant sophisticated style',
    };

    const styleText = input.style ? styleMap[input.style] : 'casual everyday wear';

    return `Please convert this photo into a highly detailed character model wearing ${input.clothingDescription}.
Style: ${styleText}.
Place in front of the model a delicate, colorful box featuring the printed image of the person from the photo.
Behind the box, show a computer screen actively displaying the modeling process.
In front of the box, position the completed character model based on the provided photo, with the ${styleText} clothing clearly and realistically rendered.
Set the entire scene in a bright, stylish indoor environment resembling a toy collector's or hobbyist's room—full of refined details, vibrant décor, and playful atmosphere.
Ensure the lighting is crisp and luminous, highlighting both the model and its packaging.`;
  }
}

4.2 抠图模板实现

// src/templates/video/image-to-video.template.ts
import { Injectable } from '@nestjs/common';
import { VideoGenerateTemplate, VideoGenerateInput, VideoGenerateOutput } from '../base/video-generate-template.abstract';
import { TemplateMetadata, TemplateExecutionContext } from '../base/template.abstract';
import { ParameterDefinition, ParameterType } from '../base/parameter.interface';
import { BowongAIService } from '../../services/bowongai.service';

interface ImageToVideoInput extends VideoGenerateInput {
  inputImage: string; // 必填
  videoPrompt?: string; // 视频生成提示词
}

@Injectable()
export class ImageToVideoTemplate extends VideoGenerateTemplate {
  readonly metadata: TemplateMetadata = {
    code: 'image_to_video_v1',
    name: '图生视频',
    description: '上传图片,生成动态视频效果',
    category: '视频生成',
    creditCost: 50,
    version: '1.0.0',
    previewImageUrl: '/images/templates/image-to-video-preview.jpg',
  };

  constructor(private readonly bowongAI: BowongAIService) {
    super();
  }

  async execute(input: ImageToVideoInput, context: TemplateExecutionContext): Promise<VideoGenerateOutput> {
    // 构建图生视频专用提示词
    const imagePrompt = this.buildImagePrompt(input);
    const videoPrompt = this.buildVideoPrompt(input);

    const result = await this.bowongAI.imageToVideo({
      imagePrompt,
      videoPrompt,
      imageUrl: input.inputImage,
      duration: input.duration || 6,
      aspectRatio: input.resolution || '9:16',
      imageModel: 'gemini-2.5-flash-image-preview',
      videoModel: '302/MiniMax-Hailuo-02',
    });

    return {
      videoUrl: result.videoUrl,
      thumbnailUrl: result.thumbnailUrl,
      duration: result.duration,
      fps: result.fps,
      resolution: result.resolution,
      format: 'mp4',
    };
  }

  protected async validateCustomInput(input: ImageToVideoInput): Promise<string[]> {
    const errors: string[] = [];

    if (!input.inputImage) {
      errors.push('必须提供原始图片');
    }

    if (input.videoPrompt && input.videoPrompt.length > 200) {
      errors.push('视频提示词不能超过200个字符');
    }

    return errors;
  }

  getParameterDefinitions(): ParameterDefinition[] {
    return [
      {
        key: 'inputImage',
        displayName: '原始图片',
        description: '请上传要生成视频的图片',
        type: ParameterType.IMAGE,
        validation: { required: true },
        order: 1,
      },
      {
        key: 'videoPrompt',
        displayName: '视频提示词',
        description: '描述想要的视频效果(可选)',
        type: ParameterType.STRING,
        validation: { maxLength: 200 },
        order: 2,
      },
      {
        key: 'resolution',
        displayName: '视频比例',
        description: '选择视频比例',
        type: ParameterType.SELECT,
        defaultValue: '9:16',
        validation: { options: ['9:16', '16:9'] },
        order: 3,
      },
      ...this.getCommonParameterDefinitions(),
    ];
  }

  private buildImagePrompt(input: ImageToVideoInput): string {
    return `Please convert this photo into a highly detailed character model.
Place in front of the model a delicate, colorful box featuring the printed image of the person from the photo.
Behind the box, show a computer screen actively displaying the Blender modeling process.
In front of the box, position the completed character model based on the provided photo, with the PVC texture clearly and realistically rendered.
Set the entire scene in a bright, stylish indoor environment resembling a toy collector's or hobbyist's room—full of refined details, vibrant décor, and playful atmosphere.
Ensure the lighting is crisp and luminous, highlighting both the model and its packaging.`;
  }

  private buildVideoPrompt(input: ImageToVideoInput): string {
    const baseVideoPrompt = 'Create gentle natural movements and subtle animations';

    if (input.videoPrompt) {
      return `${baseVideoPrompt}. ${input.videoPrompt}. Maintain character consistency throughout the animation.`;
    }

    return `${baseVideoPrompt}. Natural lifelike movement with smooth transitions.`;
  }
}

4.3 人像增强模板实现(示例)

// src/templates/image/portrait-enhance.template.ts
import { Injectable } from '@nestjs/common';
import { ImageGenerateTemplate, ImageGenerateInput, ImageGenerateOutput } from '../base/image-generate-template.abstract';
import { TemplateMetadata, TemplateExecutionContext } from '../base/template.abstract';
import { ParameterDefinition, ParameterType } from '../base/parameter.interface';
import { BowongAIService } from '../../services/bowongai.service';

interface PortraitEnhanceInput extends ImageGenerateInput {
  inputImage: string; // 必填
  enhanceLevel: 'subtle' | 'moderate' | 'dramatic';
  lightingStyle?: 'natural' | 'studio' | 'soft' | 'dramatic';
}

@Injectable()
export class PortraitEnhanceTemplate extends ImageGenerateTemplate {
  readonly metadata: TemplateMetadata = {
    code: 'portrait_enhance_v1',
    name: '人像增强',
    description: '专业级人像照片增强,提升画质和美观度',
    category: '人像处理',
    creditCost: 12,
    version: '1.0.0',
    previewImageUrl: '/images/templates/portrait-enhance-preview.jpg',
  };

  constructor(private readonly bowongAI: BowongAIService) {
    super();
  }

  async execute(input: PortraitEnhanceInput, context: TemplateExecutionContext): Promise<ImageGenerateOutput> {
    // 构建人像增强专用提示词
    const prompt = this.buildPortraitPrompt(input);

    // 调用BowongAI基础服务传入人像增强模板的特定参数
    const result = await this.bowongAI.imageToImage({
      prompt,
      imageUrl: input.inputImage,
      model: 'gemini-2.5-flash-image-preview', // 人像增强专用模型
    });

    return {
      images: result.images.map(img => ({
        url: img.url,
        thumbnailUrl: img.thumbnailUrl,
        width: img.width,
        height: img.height,
        format: 'png',
      })),
      parameters: {
        prompt,
        model: 'gemini-2.5-flash-image-preview',
        workflow: '图生图',
        templateType: 'portrait_enhance',
        enhanceLevel: input.enhanceLevel,
      },
    };
  }

  protected async validateCustomInput(input: PortraitEnhanceInput): Promise<string[]> {
    const errors: string[] = [];

    if (!input.inputImage) {
      errors.push('必须提供人像图片');
    }

    const validEnhanceLevels = ['subtle', 'moderate', 'dramatic'];
    if (!validEnhanceLevels.includes(input.enhanceLevel)) {
      errors.push('增强级别必须是: subtle, moderate, dramatic 之一');
    }

    return errors;
  }

  getParameterDefinitions(): ParameterDefinition[] {
    return [
      {
        key: 'inputImage',
        displayName: '人像图片',
        description: '请上传需要增强的人像照片',
        type: ParameterType.IMAGE,
        validation: { required: true },
        order: 1,
      },
      {
        key: 'enhanceLevel',
        displayName: '增强级别',
        description: '选择增强程度',
        type: ParameterType.SELECT,
        defaultValue: 'moderate',
        validation: { options: ['subtle', 'moderate', 'dramatic'] },
        order: 2,
      },
      {
        key: 'lightingStyle',
        displayName: '光照风格',
        description: '选择光照效果(可选)',
        type: ParameterType.SELECT,
        defaultValue: 'natural',
        validation: { options: ['natural', 'studio', 'soft', 'dramatic'] },
        order: 3,
      },
      ...this.getCommonParameterDefinitions(),
    ];
  }

  private buildPortraitPrompt(input: PortraitEnhanceInput): string {
    const enhancementLevels = {
      subtle: 'Apply subtle professional enhancements',
      moderate: 'Apply moderate quality improvements',
      dramatic: 'Apply dramatic professional transformation',
    };

    const lightingStyles = {
      natural: 'natural lighting',
      studio: 'professional studio lighting',
      soft: 'soft diffused lighting',
      dramatic: 'dramatic cinematic lighting',
    };

    const enhancement = enhancementLevels[input.enhanceLevel] || 'moderate';
    const lighting = lightingStyles[input.lightingStyle] || 'natural lighting';

    return `${enhancement} to this portrait photo.
Improve skin texture, clarity, and overall quality while maintaining natural appearance.
Focus on professional photography quality with ${lighting}.
Preserve the person's unique features and expressions.
Enhance details without over-processing or creating artificial effects.`;
  }
}

5. 模板服务集成

5.1 模板服务 (TemplateService)

// src/services/template.service.ts
import { Injectable } from '@nestjs/common';
import { TemplateManager } from './template-manager.service';
import { TemplateFactory } from '../templates/template.factory';
import { CreditService } from './credit.service';
import { TemplateExecutionContext } from '../templates/base/template.abstract';

@Injectable()
export class TemplateService {
  constructor(
    private readonly templateManager: TemplateManager,
    private readonly templateFactory: TemplateFactory,
    private readonly creditService: CreditService,
  ) {
    // 初始化时注册所有模板
    this.initializeTemplates();
  }

  // 执行模板(带积分检查)
  async executeTemplate(
    templateCode: string,
    input: any,
    userId: string,
    platform: string
  ) {
    // 1. 获取模板信息
    const template = this.templateManager.getTemplate(templateCode);
    if (!template) {
      throw new Error(`模板 ${templateCode} 不存在`);
    }

    const metadata = template.getMetadata();

    // 2. 检查用户积分
    const hasEnoughCredits = await this.creditService.checkBalance(
      userId,
      platform as any,
      metadata.creditCost
    );

    if (!hasEnoughCredits) {
      throw new Error('积分不足');
    }

    // 3. 执行模板
    const context: TemplateExecutionContext = {
      userId,
      platform,
      requestId: `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
      timestamp: new Date(),
    };

    const result = await this.templateManager.executeTemplate(templateCode, input, context);

    // 4. 扣除积分(仅在成功时)
    if (result.success) {
      await this.creditService.consumeCredits(
        userId,
        platform as any,
        metadata.creditCost,
        'ai_generation' as any,
        context.requestId
      );
    }

    return result;
  }

  // 获取所有可用模板
  getAvailableTemplates() {
    return this.templateManager.getEnabledTemplates();
  }

  // 按分类获取模板
  getTemplatesByCategory(category: string) {
    return this.templateManager.getTemplatesByCategory(category);
  }

  // 搜索模板
  searchTemplates(keyword: string) {
    return this.templateManager.searchTemplates(keyword);
  }

  // 获取模板详情
  getTemplateInfo(templateCode: string) {
    const template = this.templateManager.getTemplate(templateCode);
    if (!template) {
      return null;
    }

    return {
      ...template.getMetadata(),
      enabled: template.isEnabled(),
      parameters: template.getParameterDefinitions(),
    };
  }

  // 获取统计信息
  getStatistics() {
    return this.templateManager.getStatistics();
  }

  // 初始化模板
  private initializeTemplates() {
    const templates = this.templateFactory.createAllTemplates();
    this.templateManager.registerTemplates(templates);
  }
}

5.2 模板控制器

// src/controllers/template.controller.ts
import { Controller, Get, Post, Body, Param, Query, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import { TemplateService } from '../services/template.service';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { CurrentUser } from '../decorators/current-user.decorator';

@ApiTags('🎨 AI模板系统')
@Controller('templates')
export class TemplateController {
  constructor(private readonly templateService: TemplateService) {}

  @Get()
  @ApiOperation({ summary: '获取所有可用模板' })
  async getAvailableTemplates(@Query('category') category?: string) {
    if (category) {
      return this.templateService.getTemplatesByCategory(category);
    }
    return this.templateService.getAvailableTemplates();
  }

  @Get('search')
  @ApiOperation({ summary: '搜索模板' })
  async searchTemplates(@Query('keyword') keyword: string) {
    if (!keyword) {
      throw new Error('搜索关键词不能为空');
    }
    return this.templateService.searchTemplates(keyword);
  }

  @Get('statistics')
  @ApiOperation({ summary: '获取模板统计信息' })
  async getStatistics() {
    return this.templateService.getStatistics();
  }

  @Get(':templateCode')
  @ApiOperation({ summary: '获取模板详情' })
  async getTemplateInfo(@Param('templateCode') templateCode: string) {
    const templateInfo = this.templateService.getTemplateInfo(templateCode);
    if (!templateInfo) {
      throw new Error('模板不存在');
    }
    return templateInfo;
  }

  @Post(':templateCode/execute')
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth('JWT-auth')
  @ApiOperation({ summary: '执行模板' })
  async executeTemplate(
    @Param('templateCode') templateCode: string,
    @Body() input: any,
    @CurrentUser() user: any
  ) {
    return this.templateService.executeTemplate(
      templateCode,
      input,
      user.id,
      user.platform
    );
  }
}

5. AI服务集成

5.1 AI服务接口

// src/services/ai-service.interface.ts
export interface ImageToImageRequest {
  prompt: string;
  initImage: string;
  strength?: number;
  cfgScale?: number;
  steps?: number;
  width?: number;
  height?: number;
}

export interface ImageToImageResponse {
  images: Array<{
    url: string;
    thumbnailUrl?: string;
    width: number;
    height: number;
  }>;
  seed?: number;
}

export interface ImageToVideoRequest {
  prompt: string;
  image: string;
  duration?: number;
  fps?: number;
  resolution?: string;
}

export interface ImageToVideoResponse {
  videoUrl: string;
  thumbnailUrl?: string;
  duration: number;
  fps: number;
  resolution: string;
  fileSize?: number;
}

5.2 BowongAI 基础服务实现

// src/services/bowongai.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios from 'axios';

export interface BowongAIImageOptions {
  prompt: string;
  imageUrl: string;
  model?: string;
}

export interface BowongAIVideoOptions {
  imagePrompt: string;
  videoPrompt: string;
  imageUrl: string;
  duration?: number;
  aspectRatio?: string;
  imageModel?: string;
  videoModel?: string;
}

export interface BowongAIImageRequest {
  workflow: '图生图' | '图生图+生视频';
  environment: string;
  image_generation: {
    model: string;
    prompt: string;
    image_url: string;
  };
  video_generation?: {
    model: string;
    prompt: string;
    duration: string;
    aspect_ratio: string;
  };
}

export interface BowongAIResponse {
  success: boolean;
  data?: {
    image_url?: string;
    video_url?: string;
    task_id?: string;
  };
  error?: string;
}

@Injectable()
export class BowongAIService {
  private readonly webhookUrl: string;
  private readonly environment: string;

  constructor(private readonly configService: ConfigService) {
    this.webhookUrl = this.configService.get('BOWONGAI_WEBHOOK_URL',
      'https://n8n.bowongai.com/webhook/76f92dbf-785f-4add-96b5-a108174b7c14');
    this.environment = this.configService.get('BOWONGAI_ENVIRONMENT',
      'https://bowongai-test--text-video-agent-fastapi-app.modal.run');
  }

  // 通用图生图接口 - 各模板传入自己的参数
  async imageToImage(options: BowongAIImageOptions) {
    const payload: BowongAIImageRequest = {
      workflow: '图生图',
      environment: this.environment,
      image_generation: {
        model: options.model || 'gemini-2.5-flash-image-preview',
        prompt: options.prompt,
        image_url: options.imageUrl,
      },
    };

    const response = await axios.post(this.webhookUrl, payload, {
      headers: {
        'Content-Type': 'application/json',
      },
      timeout: 120000,
    });

    if (!response.data.success) {
      throw new Error(response.data.error || '图生图处理失败');
    }

    return {
      images: [{
        url: response.data.data.image_url,
        width: 1024,
        height: 1024,
      }],
    };
  }

  // 通用图生视频接口 - 各模板传入自己的参数
  async imageToVideo(options: BowongAIVideoOptions) {
    const payload: BowongAIImageRequest = {
      workflow: '图生图+生视频',
      environment: this.environment,
      image_generation: {
        model: options.imageModel || 'gemini-2.5-flash-image-preview',
        prompt: options.imagePrompt,
        image_url: options.imageUrl,
      },
      video_generation: {
        model: options.videoModel || '302/MiniMax-Hailuo-02',
        prompt: options.videoPrompt,
        duration: options.duration?.toString() || '6',
        aspect_ratio: options.aspectRatio || '9:16',
      },
    };

    const response = await axios.post(this.webhookUrl, payload, {
      headers: {
        'Content-Type': 'application/json',
      },
      timeout: 300000, // 5分钟超时
    });

    if (!response.data.success) {
      throw new Error(response.data.error || '图生视频处理失败');
    }

    return {
      videoUrl: response.data.data.video_url,
      duration: options.duration || 6,
      fps: 24,
      resolution: options.aspectRatio || '9:16',
      format: 'mp4',
    };
  }
}

5.3 AI服务接口更新

// src/services/ai-service.interface.ts - 更新接口定义
export interface ImageToImageRequest {
  prompt: string;
  initImage: string; // 图片URL或base64
  strength?: number;
  cfgScale?: number;
  steps?: number;
  width?: number;
  height?: number;
}

export interface ImageToImageResponse {
  images: Array<{
    url: string;
    thumbnailUrl?: string;
    width: number;
    height: number;
  }>;
  seed?: number;
}

export interface ImageToVideoRequest {
  prompt: string;
  image: string; // 图片URL或base64
  duration?: number; // 秒
  fps?: number;
  resolution?: string; // '720p', '1080p', '9:16', '16:9'
}

export interface ImageToVideoResponse {
  videoUrl: string;
  thumbnailUrl?: string;
  duration: number;
  fps: number;
  resolution: string;
  format: string;
  fileSize?: number;
}

6. 模块配置

6.1 模板模块配置

// src/modules/template.module.ts
import { Module } from '@nestjs/common';
import { TemplateController } from '../controllers/template.controller';
import { TemplateService } from '../services/template.service';
import { TemplateManager } from '../services/template-manager.service';
import { TemplateFactory } from '../templates/template.factory';
import { BowongAIService } from '../services/bowongai.service';
import { CreditService } from '../services/credit.service';

// 导入所有模板类
import { OutfitChangeTemplate } from '../templates/image/outfit-change.template';
import { ImageToVideoTemplate } from '../templates/video/image-to-video.template';
import { PortraitEnhanceTemplate } from '../templates/image/portrait-enhance.template';
import { StyleTransferTemplate } from '../templates/image/style-transfer.template';
import { BackgroundReplaceTemplate } from '../templates/image/background-replace.template';

@Module({
  controllers: [TemplateController],
  providers: [
    TemplateService,
    TemplateManager,
    TemplateFactory,
    BowongAIService,
    CreditService,
    // 注册所有模板类
    OutfitChangeTemplate,
    ImageToVideoTemplate,
    PortraitEnhanceTemplate,
    StyleTransferTemplate,
    BackgroundReplaceTemplate,
  ],
  exports: [TemplateService, TemplateManager],
})
export class TemplateModule {}

6.2 环境配置

// .env 配置
# BowongAI 配置
BOWONGAI_WEBHOOK_URL=https://n8n.bowongai.com/webhook/76f92dbf-785f-4add-96b5-a108174b7c14
BOWONGAI_ENVIRONMENT=https://bowongai-test--text-video-agent-fastapi-app.modal.run

# 其他AI服务配置(备用)
OPENAI_API_KEY=your-openai-api-key
STABILITY_API_KEY=your-stability-api-key
RUNWAYML_API_KEY=your-runwayml-api-key

7. 完整的使用示例

7.1 项目集成步骤

// 1. 安装依赖
npm install reflect-metadata

// 2. 在 app.module.ts 中导入模板模块
import { Module } from '@nestjs/common';
import { TemplateModule } from './modules/template.module';

@Module({
  imports: [
    // ... 其他模块
    TemplateModule,
  ],
})
export class AppModule {}

// 3. 创建新的模板类
// src/templates/image/my-custom.template.ts
@Injectable()
export class MyCustomTemplate extends ImageGenerateTemplate {
  readonly metadata: TemplateMetadata = {
    code: 'my_custom_v1',
    name: '我的自定义模板',
    description: '自定义的AI生成模板',
    category: '自定义',
    creditCost: 10,
    version: '1.0.0',
  };

  async execute(input: any, context: TemplateExecutionContext) {
    // 实现具体逻辑
    return {
      images: [/* 生成的图片 */],
    };
  }

  getParameterDefinitions(): ParameterDefinition[] {
    return [
      {
        key: 'prompt',
        displayName: '提示词',
        description: '描述想要生成的内容',
        type: ParameterType.STRING,
        validation: { required: true },
        order: 1,
      },
      // ... 其他参数
    ];
  }
}

// 4. 在工厂类中注册
// src/templates/template.factory.ts
createAllTemplates(): Template[] {
  return [
    // ... 现有模板
    new MyCustomTemplate(),
  ];
}

6. 使用示例

6.1 前端调用示例

图生图模板调用

// 前端调用换装模板(图生图)
const response = await fetch('/api/templates/outfit_change_v1/execute', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    inputImage: 'https://cdn.roasmax.cn/upload/e9066bf20fb546b09ece65d6fbcf2dd3.png',
    clothingDescription: '红色连衣裙',
    style: 'elegant'
  })
});

const result = await response.json();
if (result.success) {
  console.log('图生图成功:', result.data);
  // result.data.images[0].url 是生成的图片URL
} else {
  console.error('生成失败:', result.error);
}

图生视频模板调用

// 前端调用图生视频模板
const response = await fetch('/api/templates/image_to_video_v1/execute', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    inputImage: 'https://cdn.roasmax.cn/upload/e9066bf20fb546b09ece65d6fbcf2dd3.png',
    videoPrompt: '让角色动起来',
    duration: 6,
    resolution: '9:16'
  })
});

const result = await response.json();
if (result.success) {
  console.log('图生视频成功:', result.data);
  // result.data.videoUrl 是生成的视频URL
} else {
  console.error('生成失败:', result.error);
}

人像增强模板调用

// 前端调用人像增强模板
const response = await fetch('/api/templates/portrait_enhance_v1/execute', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    inputImage: 'https://cdn.roasmax.cn/upload/e9066bf20fb546b09ece65d6fbcf2dd3.png',
    enhanceLevel: 'moderate',
    lightingStyle: 'natural'
  })
});

const result = await response.json();
if (result.success) {
  console.log('人像增强成功:', result.data);
  // result.data.images[0].url 是增强后的图片URL
} else {
  console.error('增强失败:', result.error);
}

6.2 添加新模板

// 1. 创建新的模板类
// src/templates/image/portrait-enhance.template.ts
@Injectable()
export class PortraitEnhanceTemplate extends ImageGenerateTemplate {
  readonly metadata: TemplateMetadata = {
    code: 'portrait_enhance_v1',
    name: '人像增强',
    description: '提升人像照片的清晰度和美观度',
    category: '人像处理',
    creditCost: 8,
    version: '1.0.0',
  };

  async execute(input: any, context: TemplateExecutionContext) {
    // 实现具体的人像增强逻辑
    // ...
  }

  getParameterDefinitions(): ParameterDefinition[] {
    // 定义参数
    // ...
  }
}

// 2. 在工厂类中注册
// src/templates/template.factory.ts
export class TemplateFactory {
  createAllTemplates(): Template[] {
    return [
      new OutfitChangeTemplate(),
      new ObjectRemovalTemplate(),
      new PortraitEnhanceTemplate(), // 添加新模板
      // ...
    ];
  }
}

6.3 模板管理命令

// 获取所有模板
const templates = templateManager.getAllTemplates();

// 启用/禁用模板
templateManager.setTemplateEnabled('outfit_change_v1', false);

// 获取统计信息
const stats = templateManager.getStatistics();
console.log(`共有 ${stats.total} 个模板,其中 ${stats.enabled} 个已启用`);

7. 系统优势

7.1 面向对象设计优势

  1. 类型安全: TypeScript编译时检查避免运行时错误
  2. 代码复用: 通过继承实现通用逻辑复用
  3. 易于扩展: 添加新模板只需继承基类并实现抽象方法
  4. IDE支持: 完整的代码提示和重构支持
  5. 单元测试: 每个模板类可独立测试

7.2 与数据库方案对比

特性 面向对象方案 数据库方案
类型安全 编译时检查 运行时检查
代码复用 继承机制 需要额外逻辑
扩展性 新增类即可 需要数据库迁移
性能 内存中执行 数据库查询开销
维护性 代码即文档 需要维护数据结构
版本控制 Git管理 数据库版本管理复杂

7.3 核心特性

  1. 统一接口: 所有模板都实现相同的执行接口
  2. 参数验证: 基类提供通用验证,子类可扩展
  3. 错误处理: 统一的错误处理和结果格式
  4. 元数据管理: 每个模板包含完整的元数据信息
  5. 动态管理: 运行时启用/禁用模板
  6. 积分集成: 自动处理积分检查和扣除

这个基于面向对象的模板管理系统更加灵活、类型安全且易于维护和扩展。通过抽象类和继承机制可以轻松添加新的AI生成模板同时保持代码的一致性和可维护性。