1669 lines
48 KiB
Markdown
1669 lines
48 KiB
Markdown
# AI模板管理系统设计方案 (面向对象版本)
|
||
|
||
## 1. 系统概述
|
||
|
||
### 1.1 核心目标
|
||
- **统一接口**: 通过抽象类抹平不同AI模型和API的调用差异
|
||
- **模板化管理**: 将复杂的AI调用封装为简单的模板类
|
||
- **类型安全**: 利用TypeScript的类型系统确保参数和返回值的正确性
|
||
- **易于扩展**: 通过继承轻松添加新的模板类型
|
||
|
||
### 1.2 设计优势
|
||
- **编译时检查**: TypeScript编译时就能发现接口不匹配问题
|
||
- **代码复用**: 通过继承实现通用逻辑的复用
|
||
- **类型推导**: IDE能提供完整的代码提示和类型检查
|
||
- **易于测试**: 每个模板类都可以独立进行单元测试
|
||
|
||
### 1.3 模板类型示例
|
||
- **换装模板**: 图片 + 服装描述 → 换装后图片
|
||
- **抠图模板**: 图片 + 物体描述 → 抠图视频
|
||
- **风格转换**: 图片 + 风格描述 → 风格化图片
|
||
- **背景替换**: 图片 + 背景描述 → 新背景图片
|
||
|
||
## 2. 核心抽象类设计
|
||
|
||
### 2.1 基础模板抽象类
|
||
```typescript
|
||
// 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 参数定义接口
|
||
```typescript
|
||
// 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 图片生成模板抽象类
|
||
```typescript
|
||
// 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 视频生成模板抽象类
|
||
```typescript
|
||
// 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)
|
||
```typescript
|
||
// 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)
|
||
```typescript
|
||
// 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 换装模板实现
|
||
```typescript
|
||
// 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 抠图模板实现
|
||
```typescript
|
||
// 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 人像增强模板实现(示例)
|
||
```typescript
|
||
// 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)
|
||
```typescript
|
||
// 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 模板控制器
|
||
```typescript
|
||
// 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服务接口
|
||
```typescript
|
||
// 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 基础服务实现
|
||
```typescript
|
||
// 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服务接口更新
|
||
```typescript
|
||
// 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 模板模块配置
|
||
```typescript
|
||
// 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 环境配置
|
||
```typescript
|
||
// .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 项目集成步骤
|
||
```typescript
|
||
// 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 前端调用示例
|
||
|
||
#### 图生图模板调用
|
||
```typescript
|
||
// 前端调用换装模板(图生图)
|
||
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);
|
||
}
|
||
```
|
||
|
||
#### 图生视频模板调用
|
||
```typescript
|
||
// 前端调用图生视频模板
|
||
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);
|
||
}
|
||
```
|
||
|
||
#### 人像增强模板调用
|
||
```typescript
|
||
// 前端调用人像增强模板
|
||
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 添加新模板
|
||
```typescript
|
||
// 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 模板管理命令
|
||
```typescript
|
||
// 获取所有模板
|
||
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生成模板,同时保持代码的一致性和可维护性。
|