造型生成网站 - 设计稿
1. 目标与范围
1.1 项目目标
- 核心目标:用户上传照片,系统生成"新造型图像 + 造型理由",形成可展示的前后对比与文字说明
- 技术目标:构建可扩展的 AI 模型调用架构,支持灵活切换不同 Vision LLM 和生图模型
1.2 MVP 范围
| 包含 |
不包含(后期迭代) |
| 单人头像/半身照输入 |
多人合照处理 |
| 1 张新造型图 + 1 段中文说明 |
多风格批量生成 |
| 基础上传与展示 |
复杂编辑工具 |
| 模型可配置切换 |
用户账号体系 |
| 临时图片存储 |
历史记录持久化 |
2. 用户流程(前台)
2.1 主流程
2.2 页面结构
/ # 首页(上传入口)
/result/:taskId # 结果页(支持分享链接)
3. 系统架构(后台)
3.1 整体架构图
3.2 核心组件说明
| 组件 |
职责 |
技术选型 |
| Frontend |
上传、进度展示、结果渲染 |
Next.js 14 + React 18 + TailwindCSS |
| Backend API |
请求处理、任务编排、结果返回 |
Node.js + Express + TypeScript |
| Task Orchestrator |
编排 AI 调用流程 |
自研状态机 |
| Model Adapter |
统一模型调用接口 |
适配器模式 |
| Image Processor |
图片压缩、格式转换、裁剪 |
sharp |
| Storage Service |
临时文件存储 |
本地文件系统 / S3 兼容存储 |
4. 模型配置设计(核心)
4.1 配置文件结构
YAML
vision:
active: "openai"
providers:
openai:
name: "GPT-4o"
endpoint: "https://api.openai.com/v1/chat/completions"
model: "gpt-4o"
apiKeyEnv: "OPENAI_API_KEY"
maxTokens: 2000
temperature: 0.7
timeout: 60000
anthropic:
name: "Claude 3.5 Sonnet"
endpoint: "https://api.anthropic.com/v1/messages"
model: "claude-3-5-sonnet-20241022"
apiKeyEnv: "ANTHROPIC_API_KEY"
maxTokens: 2000
timeout: 60000
google:
name: "Gemini 1.5 Pro"
endpoint: "https://generativelanguage.googleapis.com/v1beta/models"
model: "gemini-1.5-pro"
apiKeyEnv: "GOOGLE_API_KEY"
maxTokens: 2000
timeout: 60000
alibaba:
name: "Qwen-VL-Max"
endpoint: "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"
model: "qwen-vl-max"
apiKeyEnv: "DASHSCOPE_API_KEY"
maxTokens: 2000
timeout: 60000
imageGen:
active: "openai"
providers:
openai:
name: "DALL-E 3"
endpoint: "https://api.openai.com/v1/images/generations"
model: "dall-e-3"
apiKeyEnv: "OPENAI_API_KEY"
size: "1024x1024"
quality: "hd"
timeout: 120000
replicate_flux:
name: "Flux 1.1 Pro"
endpoint: "https://api.replicate.com/v1/predictions"
model: "black-forest-labs/flux-1.1-pro"
apiKeyEnv: "REPLICATE_API_TOKEN"
aspectRatio: "1:1"
outputFormat: "webp"
timeout: 120000
replicate_sdxl:
name: "SDXL + InstantID"
endpoint: "https://api.replicate.com/v1/predictions"
model: "zsxkib/instant-id"
apiKeyEnv: "REPLICATE_API_TOKEN"
timeout: 180000
requiresFaceImage: true
fal_flux:
name: "Fal Flux Pro"
endpoint: "https://fal.run/fal-ai/flux-pro"
apiKeyEnv: "FAL_KEY"
imageSize: "square_hd"
timeout: 120000
midjourney:
name: "Midjourney (via Proxy)"
endpoint: "${MIDJOURNEY_PROXY_URL}"
apiKeyEnv: "MIDJOURNEY_API_KEY"
timeout: 300000
pollingMode: true
pollingInterval: 5000
faceDetection:
enabled: true
provider: "local"
providers:
local:
modelPath: "./models/face-api"
minConfidence: 0.5
cloud:
endpoint: "${FACE_API_ENDPOINT}"
apiKeyEnv: "FACE_API_KEY"
common:
retry:
maxAttempts: 3
backoffMs: 1000
backoffMultiplier: 2
rateLimit:
maxConcurrent: 10
requestsPerMinute: 30
4.2 环境变量配置
OPENAI_API_KEY=sk-xxx
ANTHROPIC_API_KEY=sk-ant-xxx
GOOGLE_API_KEY=AIza-xxx
DASHSCOPE_API_KEY=sk-xxx
REPLICATE_API_TOKEN=r8_xxx
FAL_KEY=xxx
MIDJOURNEY_PROXY_URL=https://your-mj-proxy.com
MIDJOURNEY_API_KEY=xxx
FACE_API_ENDPOINT=
FACE_API_KEY=
STORAGE_TYPE=local # local / s3
STORAGE_PATH=./uploads
PORT=3001
NODE_ENV=development
4.3 模型适配器接口设计
interface VisionAnalysisRequest {
imageBase64: string;
mimeType: string;
prompt: string;
}
interface VisionAnalysisResponse {
imagePrompt: string;
reasoning: string;
rawResponse: string;
usage?: {
promptTokens: number;
completionTokens: number;
};
interface VisionProvider {
name: string;
analyze(request: VisionAnalysisRequest): Promise<VisionAnalysisResponse>;
healthCheck(): Promise<boolean>;
}
interface ImageGenerationRequest {
prompt: string;
referenceImageBase64?: string;
size?: string;
style?: string;
}
interface ImageGenerationResponse {
imageUrl: string;
imageBase64?: string;
revisedPrompt?: string;
}
interface ImageGenProvider {
name: string;
generate(request: ImageGenerationRequest): Promise<ImageGenerationResponse>;
healthCheck(): Promise<boolean>;
}
4.4 模型工厂模式
TypeScript
class ModelFactory {
private visionProviders: Map<string, VisionProvider>;
private imageGenProviders: Map<string, ImageGenProvider>;
private config: ModelConfig;
constructor(configPath: string) {
this.config = this.loadConfig(configPath);
this.visionProviders = new Map();
this.imageGenProviders = new Map();
this.initializeProviders();
}
getVisionProvider(): VisionProvider {
const active = this.config.vision.active;
return this.visionProviders.get(active);
}
getImageGenProvider(): ImageGenProvider {
const active = this.config.imageGen.active;
return this.imageGenProviders.get(active);
}
switchVisionProvider(providerName: string): void;
switchImageGenProvider(providerName: string): void;
listProviders(): { vision: string[], imageGen: string[] };
}
5. 核心流程设计
5.1 主流程时序图
5.2 任务状态机
6. 元提示词设计
6.1 系统提示词模板
systemPrompt: |
你是一位世界顶级的发型设计师与形象顾问,拥有20年服务名人与普通客户的经验。
你擅长根据客户的脸型、五官特征、气质类型,设计最适合的发型与整体造型方案。
分析用户上传的照片,为其设计一个全新的造型方案,并提供专业的设计理由。
1. **脸型分析**:椭圆/圆形/方形/长形/心形/菱形
2. **五官特征**:眼睛大小、鼻型、嘴唇、额头高度、下颌线条
3. **当前状态**:现有发型、发质推测、整体风格
4. **气质类型**:知性/甜美/帅气/成熟/清新/...
请严格按照以下格式输出,使用分隔符分隔:
(这里输出英文的图像生成提示词,要求:
- 必须强调 "same person, preserve exact facial features, same face"
- 详细描述新发型:长度、层次、刘海、颜色、质感
- 描述服装风格(如适用)
- 指定摄影风格:lighting, camera angle, background
- 指定图像质量:professional photography, 8k, detailed
- 示例结构:A portrait of the same person with [新发型描述], wearing [服装], [摄影风格], [质量词])
(这里输出中文的造型设计说明,包含:
- 脸型与五官分析结果
- 为什么推荐这个发型(解决什么问题/强化什么优点)
- 新造型会带来的气质变化
- 日常打理建议(可选)
- 总字数控制在 150-250 字)
userPrompt: |
请分析这张照片中的人物,为 TA 设计一个全新的造型方案。
6.2 输出解析器
interface ParsedResponse {
imagePrompt: string;
reasoning: string;
parseSuccess: boolean;
errors?: string[];
}
function parseVisionResponse(rawResponse: string): ParsedResponse {
const imagePromptMatch = rawResponse.match(
/###IMAGE_PROMPT###\s*([\s\S]*?)(?=###REASONING###|$)/
);
const reasoningMatch = rawResponse.match(
/###REASONING###\s*([\s\S]*?)$/
);
const result: ParsedResponse = {
imagePrompt: imagePromptMatch?.[1]?.trim() || '',
reasoning: reasoningMatch?.[1]?.trim() || '',
parseSuccess: true,
errors: []
};
if (!result.imagePrompt) {
result.parseSuccess = false;
result.errors.push('Missing IMAGE_PROMPT section');
}
if (!result.reasoning) {
result.parseSuccess = false;
result.errors.push('Missing REASONING section');
}
return result;
}
7. API 接口设计
7.1 接口清单
POST /api/upload:
description: 上传图片并创建任务
request:
type: multipart/form-data
fields:
image: File (required, max 10MB, jpg/png/webp)
response:
200:
taskId: string
status: "pending"
message: "任务已创建"
400:
error: "INVALID_IMAGE" | "NO_FACE_DETECTED" | "MULTIPLE_FACES"
message: string
429:
error: "RATE_LIMITED"
message: string
GET /api/status/:taskId:
description: 查询任务状态
response:
200:
taskId: string
status: "pending" | "analyzing" | "generating" | "completed" | "failed"
progress: number (0-100)
message: string
resultUrl?: string
error?: string
404:
error: "TASK_NOT_FOUND"
GET /api/result/:taskId:
description: 获取任务结果
response:
200:
taskId: string
originalImageUrl: string
generatedImageUrl: string
reasoning: string
createdAt: string
expiresAt: string
404:
error: "TASK_NOT_FOUND" | "RESULT_EXPIRED"
GET /api/image/:imageId:
description: 获取图片(代理/签名URL)
response:
200: Binary (image/*)
404: Not Found
POST /api/admin/switch-model:
description: 切换模型(管理接口)
headers:
Authorization: Bearer <admin_token>
request:
type: "vision" | "imageGen"
provider: string
response:
200:
success: true
activeProvider: string
7.2 错误码规范
export const ErrorCodes = {
INVALID_IMAGE_FORMAT: { code: 1001, message: '不支持的图片格式,请上传 JPG/PNG/WebP' },
IMAGE_TOO_LARGE: { code: 1002, message: '图片过大,请上传 10MB 以内的图片' },
IMAGE_TOO_SMALL: { code: 1003, message: '图片分辨率过低,请上传更清晰的照片' },
NO_FACE_DETECTED: { code: 1004, message: '未检测到人脸,请上传包含清晰正面人脸的照片' },
MULTIPLE_FACES: { code: 1005, message: '检测到多张人脸,请上传单人照片' },
FACE_TOO_SMALL: { code: 1006, message: '人脸区域过小,请上传脸部更清晰的照片' },
TASK_NOT_FOUND: { code: 2001, message: '任务不存在' },
TASK_EXPIRED: { code: 2002, message: '任务已过期' },
TASK_IN_PROGRESS: { code: 2003, message: '任务处理中,请稍候' },
VISION_ANALYSIS_FAILED: { code: 3001, message: 'AI 分析失败,请重试' },
IMAGE_GENERATION_FAILED: { code: 3002, message: '图像生成失败,请重试' },
MODEL_UNAVAILABLE: { code: 3003, message: 'AI 服务暂时不可用' },
CONTENT_POLICY_VIOLATION: { code: 3004, message: '图片内容不符合使用规范' },
RATE_LIMITED: { code: 4001, message: '请求过于频繁,请稍后再试' },
SERVER_ERROR: { code: 4002, message: '服务器错误,请稍后重试' },
SERVICE_UNAVAILABLE: { code: 4003, message: '服务维护中' },
} as const;
8. 数据模型
8.1 任务模型
interface Task {
id: string;
status: TaskStatus;
progress: number;
originalImage: {
path: string;
url: string;
mimeType: string;
size: number;
};
analysis?: {
imagePrompt: string;
reasoning: string;
rawResponse: string;
completedAt: Date;
};
generation?: {
imageUrl: string;
localPath: string;
revisedPrompt?: string;
completedAt: Date;
};
error?: {
code: number;
message: string;
details?: string;
stage: 'upload' | 'analysis' | 'generation';
};
createdAt: Date;
updatedAt: Date;
expiresAt: Date;
modelConfig: {
visionProvider: string;
imageGenProvider: string;
};
type TaskStatus =
| 'pending'
| 'analyzing'
| 'generating'
| 'completed'
| 'failed';
8.2 存储策略
本地存储结构:
uploads/
├── tasks/
│ ├── {taskId}/
│ │ ├── original.jpg # 原图
│ │ ├── generated.jpg # 生成图
│ │ └── metadata.json # 任务元数据
│ └── ...
└── temp/ # 临时文件(处理中)
清理策略:
- 已完成任务: 24 小时后清理
- 失败任务: 6 小时后清理
- 临时文件: 1 小时后清理
9. 前端设计
9.1 页面组件结构
src/
├── app/
│ ├── page.tsx # 首页
│ ├── result/[taskId]/
│ │ └── page.tsx # 结果页
│ └── layout.tsx
├── components/
│ ├── upload/
│ │ ├── DropZone.tsx # 拖拽上传区
│ │ ├── ImagePreview.tsx # 上传预览
│ │ └── UploadButton.tsx
│ ├── progress/
│ │ ├── ProgressBar.tsx # 进度条
│ │ ├── StatusMessage.tsx # 状态文字
│ │ └── LoadingAnimation.tsx
│ ├── result/
│ │ ├── BeforeAfter.tsx # 前后对比
│ │ ├── ReasoningCard.tsx # 造型说明
│ │ └── ActionButtons.tsx # 下载/分享/重试
│ └── common/
│ ├── Header.tsx
│ ├── Footer.tsx
│ └── ErrorBoundary.tsx
├── hooks/
│ ├── useUpload.ts
│ ├── useTaskStatus.ts # 轮询任务状态
│ └── useImagePreload.ts
├── lib/
│ ├── api.ts # API 调用封装
│ └── utils.ts
└── styles/
└── globals.css
9.2 状态管理流程
interface TaskState {
taskId: string | null;
status: TaskStatus;
progress: number;
message: string;
result: TaskResult | null;
error: TaskError | null;
}
function useTaskStatus(taskId: string | null) {
const [state, setState] = useState<TaskState>(initialState);
useEffect(() => {
if (!taskId) return;
const pollInterval = setInterval(async () => {
const response = await api.getTaskStatus(taskId);
setState(prev => ({
...prev,
status: response.status,
progress: response.progress,
message: getStatusMessage(response.status),
}));
if (response.status === 'completed') {
clearInterval(pollInterval);
const result = await api.getTaskResult(taskId);
setState(prev => ({ ...prev, result }));
}
if (response.status === 'failed') {
clearInterval(pollInterval);
setState(prev => ({ ...prev, error: response.error }));
}
}, 2000);
return () => clearInterval(pollInterval);
}, [taskId]);
return state;
}
10. 安全与性能
10.1 安全措施
| 风险 |
措施 |
| 恶意文件上传 |
文件类型白名单 + magic bytes 校验 |
| 图片内容违规 |
可接入内容审核 API(腾讯云/阿里云) |
| API 滥用 |
基于 IP 的速率限制 + 可选验证码 |
| 敏感数据泄露 |
任务 ID 使用 UUID、图片 URL 签名 |
| 隐私保护 |
默认 24h 自动清理、不做持久化存储 |
10.2 性能优化
| 场景 |
策略 |
| 图片上传 |
前端压缩至 2048px max、使用 WebP |
| 长任务等待 |
轮询间隔动态调整(2s → 5s) |
| 结果页加载 |
图片渐进式加载 + 骨架屏 |
| API 响应 |
压缩响应、CDN 加速静态资源 |
11. 部署架构
11.1 开发环境
本地开发:
- Node.js 18+
- pnpm
- 本地文件存储
11.2 生产环境
┌─────────────────────────────────────────────────────────┐
│ CDN │
│ (静态资源) │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────┐
│ Load Balancer │
│ (Nginx/云LB) │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ App Node │ │ App Node │ │ App Node │
│ (PM2) │ │ (PM2) │ │ (PM2) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└────────────────┬┼─────────────────┘
││
┌───────────────┘└───────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Redis │ │ S3/MinIO │
│ (任务状态) │ │ (文件存储) │
└──────────────┘ └──────────────┘
VibeCoding 导航:⬅️ 02-页面清单 | 01-造型生成网站 - 设计稿 | ➡️ 02-计划书
💬 评论