Files
ai-toy-patent-workflow/HANDOFF_IMAGE_PIPELINE.md

443 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 生图链路重构交接文档
> 给后续 AI 开发者的实施清单。当前代码的整体骨架已搭好模板、Pack、Manifest、Seedance、GPT provider但**一致性机制是假的**,需要按本文档重构。
>
> 路径:`/Users/kangwan/Projects/code/20260518-ai-toy-patent-workflow/HANDOFF_IMAGE_PIPELINE.md`
---
## 0. 一句话目标
让"上传图 + 风格 → 意向图 → 选中主方案 → 专利包 → 配件包 → 生产包 → 宣发包 → 视频"的整条链路里,**每张图都基于上游锚图生成、每张图都能单独重做且不脱链**。专利申请要求前后一致,配件六视图也必须自成体系。
---
## 1. 用户期望的目标流程
```
[上传图(单张/多张) + 选风格(风格库可视化)]
批量生图4/8/12 张候选)
九宫格快筛 → 选中主方案
锁定 CharacterSpec角色基线+ 生成 L1 净化锚图
按顺序生成(每步可单独重做,但参考链不能脱):
├─ 专利包(六视图 + 立体图 + 局部图)
├─ 配件包(先识别配件 → 每件孤立锚图 → 每件 6 视图 → 组合图)
├─ 生产包(尺寸/材料/拆件/包装)
├─ 宣发包(白底/场景/卖点/详情页)
└─ Seedance 视频(用宣发白底图当锚)
```
**核心约束**
1. 风格库要可视化(缩略图代表)+ 内容完整lighting/composition/material/negative
2. 每张图都有明确的上游 anchor参考链不能跨级
3. 单张重做必须沿用同一个 anchor
4. 配件六视图必须基于配件孤立锚图,不是娃娃源图
5. 视频参考宣发白底图,不是意向图
---
## 2. 现状的 6 个关键 Gap
### Gap 1参考图根本没传给模型最严重
**位置**`src/lib/providers.ts:62-65`
```typescript
const refHint = opts.refImages?.length
? `\n参考图 URL用于保持角色一致\n${opts.refImages.join('\n')}`
: '';
```
**问题**:把参考图 URL 当文本拼在 prompt 末尾发给 `/images/generations`。这个端点根本不读图,模型只看到一串文本 URL**等于没参考**。
**真正的图生图**必须走 `/images/edits` + multipart 上传图像字节,或者用 `/responses` + vision input 让 GPT 先描述再二次生图。
### Gap 2风格库太薄、看不到样子
**位置**`src/components/PromptPanel.tsx:5-12`
只有 6 个文字按钮:`毛绒玩偶 / 机甲风 / 可爱萌系 / 专利蓝图 / 赛博朋克 / 极简`
**问题**
- 没有代表缩略图(用户看不到"这个风格长什么样"
- 没有完整的 style blocklighting、composition、color palette、material hint、negative prompt
- 只是简单拼成 `prompt + ", style: 机甲风"`
### Gap 3所有后续图都参考"最初那张意向图",逐级漂移
**位置**`src/lib/packGenerator.ts:144`
```typescript
const prompt = renderPrompt(template.promptTemplate, characterSpec, opts.sourceImage.url);
```
**问题**:专利右视图、宣发场景图、配件六视图……全部参考同一张意向图。
**正确做法 — 锚图链**
```
L0 锚图 = 用户选中的意向图(可能还有背景、不够干净)
L1 锚图 = L0 净化后的白底正面图CharacterSpec 锁定时生成)
L2 锚图 = 各包的首图patent_front / acc_inventory / mkt_white_front
L3 节点 = pack 内其他图都参考自己包的 L2
```
现在所有 L2/L3 都跨过 L1 直接参考 L0**6-30 张图相互之间没有锚定,越生越漂**。
### Gap 4配件链路是假的
**位置**`src/lib/packGenerator.ts:36-37`
```typescript
if (prompt.includes('机甲')) accessories.unshift('机甲头盔');
if (prompt.includes('M logo')) accessories.unshift('胸前 M 标识');
```
**问题**
- 配件清单是**关键词硬匹配**,换个 prompt 就识别不出
- 配件六视图都把"主娃娃源图"当 ref配件本身轮廓、材质会被娃娃身体盖住
**正确做法**
1. 锁定主方案后,调 GPT Vision 解析 L1 锚图 → 输出 `[{name, isolatedDescription, bbox}]`
2. 为每个配件生成**配件孤立锚图**(白底、单件、无娃娃)
3. 配件六视图基于自己的孤立锚图
4. 最后单独生成"配件+娃娃组合图"
### Gap 5单张重做会脱链
代码里只有 `generateAssetPack`(整包重做),**没有单张重做接口**。
如果右视图不满意要单独重做,需要:
- 知道这张图的锚图是谁(同包主视图)
- 锚图必须存在且未变
- 可选传 `userRefinement` 文本补充
需要新增 API `POST /api/assets/[assetId]/regenerate`,并在 `ToyAsset` 类型上加 `anchorAssetId` 字段。
### Gap 6视频参考也不一致
**位置**`src/app/page.tsx:161`
```typescript
imageUrl: image.url // 还是那张原始意向图
```
**问题**:视频里的玩具和已定稿的电商图长得不一样。
**正确做法**:视频参考宣发白底主图(`mkt_white_front`),如果未生成就退到专利主图。
---
## 3. 实施方案(按依赖顺序)
### 阶段 1真图生图链路最高优先级1+2+3+5 是最小可用版本)
#### 1.1 新增 `generateGptImageEdit`providers.ts
```typescript
export async function generateGptImageEdit(opts: {
prompt: string;
anchorImage: Buffer | string; // 真实图片字节或本地路径
maskImage?: Buffer; // 可选 mask局部重绘
size?: '1024x1024' | '1024x1536' | '1536x1024';
}): Promise<GenImage>
```
实现要点:
-`https://api.openai.com/v1/images/edits`
- `multipart/form-data``image` (Buffer)、`prompt``model=gpt-image-1``size`
- **必须传图字节而不是 URL**
- 返回 `b64_json` → 解码为 data URL
保留现有 `generateGptImages` 用于 **L0 意向图阶段**(无锚图,纯文本生图)。
#### 1.2 数据模型扩展types.ts
```typescript
export type ToyAsset = {
// ...existing
anchorAssetId?: string; // 上游锚图 asset id
anchorImageUrl?: string; // 解析后的锚图实际 URL
derivationLevel: 0 | 1 | 2 | 3;
};
export type CharacterSpec = {
// ...existing
cleanReferenceImageUrl?: string; // L1 净化锚图(白底正面)
};
export type AssetTemplate = {
// ...existing
anchorTemplateId?: string; // 显式指明上游锚图模板
};
```
#### 1.3 模板加 anchorTemplateIdtemplates.ts
```typescript
{ id: 'patent_front', anchorTemplateId: undefined, ... } // 用 L1 锚图
{ id: 'patent_back', anchorTemplateId: 'patent_front', ... } // 用包内主图
{ id: 'patent_left', anchorTemplateId: 'patent_front', ... }
// 配件同理
{ id: 'acc_inventory_sheet', anchorTemplateId: undefined, ... } // 用 L1 锚图
{ id: 'acc_front', anchorTemplateId: 'acc_inventory_sheet', ... }
{ id: 'acc_back', anchorTemplateId: 'acc_inventory_sheet', ... }
```
#### 1.4 新增 API净化锚图
```
POST /api/character/cleanup
```
逻辑:
- 输入:`sessionId + imageId`
-`generateGptImageEdit`prompt = "保持角色完全一致,把背景换成纯白色,产品居中,无任何文字水印,光线均匀"
- 输出图 URL 写回 `session.characterSpec.cleanReferenceImageUrl`
#### 1.5 改造 generateAssetPackpackGenerator.ts
```typescript
async function resolveAnchorImage(template, packAssets, characterSpec) {
if (!template.anchorTemplateId) {
// 用 L1 净化锚图,没有则退到 L0
return characterSpec.cleanReferenceImageUrl ?? characterSpec.sourceImageUrl;
}
const upstream = packAssets.find(a => a.templateId === template.anchorTemplateId);
if (!upstream) throw new Error(`anchor ${template.anchorTemplateId} not generated yet`);
return upstream.url;
}
```
按模板拓扑顺序生成(先无 anchor 的,再依次):
1. 第一张 → 走 `generateGptImageEdit(prompt, L1Buffer)`
2. 其它张 → 走 `generateGptImageEdit(prompt, 同包首图 Buffer)`
需要新增工具函数:从 URL`/api/img/packs/xxx.png`)读回 Buffer供 multipart 上传。
### 阶段 2单张重做
#### 2.1 新增 API
```
POST /api/assets/[assetId]/regenerate
Body: { sessionId, userRefinement?: string }
```
逻辑:
- 找到这个 asset 在哪个 pack
- 解析它的 anchor按 template.anchorTemplateId
-`generateGptImageEdit`prompt 末尾追加 `userRefinement`
- 替换原 asset保留 id 不变
- 更新 session JSON
#### 2.2 UI
`PackPanel.tsx` 里每个 `AssetRow` 加"重做"按钮和"refinement"输入框。
### 阶段 3风格库可视化
#### 3.1 新增 `src/lib/styles.ts`
```typescript
export type StylePreset = {
id: string;
label: string;
thumbnailUrl: string; // /styles/plush-classic.png
promptBlock: string; // 完整 style prompt 段
negativePrompt: string;
recommendedPalette: string[];
recommendedMaterials: string[];
goodFor: PackKind[];
};
```
至少 12-16 个预设,每个对应一张 256×256 缩略图放 `public/styles/`
建议初始风格列表:
- 经典毛绒、长毛毛绒、超柔短绒、卡通圆胖
- 机甲风、赛博朋克、未来科技
- 可爱萌系、治愈系、Kuromi 暗黑可爱
- 复古玩具、迪士尼风、皮克斯风
- 黏土材质、绒线编织、3D 渲染、专利蓝图
#### 3.2 改 PromptPanel
风格选择从 6 个按钮 → 4 列网格的图卡(缩略图 + 名称 + 适用包 tag
风格切换时:
- `promptBlock` 合并到生图 prompt
- `negativePrompt` 单独传给 providerGPT image edit 支持 negative
- `recommendedPalette/Materials` 自动填到 CharacterSpec 默认值
### 阶段 4配件 Vision 识别
#### 4.1 新增 `src/lib/accessoryDetector.ts`
```typescript
export type DetectedAccessory = {
id: string;
name: string;
isolatedDescription: string;
recommendedColors: string[];
approximateBBox?: { x: number; y: number; w: number; h: number };
};
export async function detectAccessories(anchorImageUrl: string): Promise<DetectedAccessory[]>
```
实现:
-`/responses` 端点 + vision inputGPT-4.1-vision 或 gpt-5.5 多模态)
- 把 L1 锚图作为图像输入
- prompt = "识别图中玩具身上所有独立配件,输出 JSON 数组,每项包含 name、isolatedDescription、recommendedColors。不包括玩具主体本身。"
- 严格 JSON 输出
#### 4.2 配件包生成流程重构
```
1. 调 detectAccessories(L1_anchor) → [帽子, 背包, 标牌, ...]
2. 把每个 accessory 加入 session.characterSpec.accessoriesDetected[]
3. 为每个 accessory 生成 isolated_anchor白底、孤立、单件
- 走 generateGptImageEdit(L1锚图, "只保留 ${name},其它部分擦除,白底,居中")
4. 每个 accessory 的 6 视图front/back/left/right/top/bottom/perspective
都基于自己的 isolated_anchor
5. 最后生成 with_doll_assembly参考 L1锚图 + isolated_anchors 组合)
```
数据模型加:
```typescript
export type AccessoryGroup = {
id: string;
name: string;
isolatedAnchorUrl: string;
views: ToyAsset[]; // 6+ 视图
};
export type AssetPack = {
// ...existing
accessoryGroups?: AccessoryGroup[]; // 仅 kind === 'accessories' 用
};
```
### 阶段 5视频参考一致性
#### 5.1 改 `handleGenerateVideo`page.tsx
```typescript
const mktFront = packs
.find(p => p.kind === 'marketing')?.assets
.find(a => a.templateId === 'mkt_white_front');
const patentFront = packs
.find(p => p.kind === 'patent')?.assets
.find(a => a.templateId === 'patent_front');
const videoAnchor =
mktFront?.url ??
patentFront?.url ??
session.characterSpec?.cleanReferenceImageUrl ??
image.url;
```
UI 上视频按钮旁边显示「参考:宣发白底图 / 专利主图 / 意向图」,用户清楚视频基于哪张。
如果宣发主图未生成,按钮可选「强制要求先生成宣发主图」或「使用专利主图」。
### 阶段 6UI 锚图链可视化
`PackPanel` 顶部"角色锁定 & 资产清单"卡片下方加一个可视化树:
```
L0 意向图 ──→ L1 白底锚图 ──┬──→ 专利主图 ──→ 专利右视图 / 左视图 / ...
├──→ 配件锚图 ──→ 帽子 6 视图 / 背包 6 视图
├──→ 宣发白底图 ──→ 视频任务
└──→ 生产主图 ──→ 尺寸图 / 拆件图
```
让用户一眼看到每张图沿用哪张作为基准,重做某个节点会影响下游哪些。
可以用简单的 flexbox 树或 SVG 连线。
---
## 4. 实施 Checklist
### 最小可用版本(先做这 4 项)
- [ ] 1.1 新增 `generateGptImageEdit`multipart upload
- [ ] 1.2 数据模型加 `anchorAssetId / anchorImageUrl / derivationLevel / cleanReferenceImageUrl / anchorTemplateId`
- [ ] 1.3 模板加 `anchorTemplateId`
- [ ] 1.5 `generateAssetPack` 按拓扑生成、用真图生图
- [ ] 1.4 `POST /api/character/cleanup` 生成 L1 锚图
### 单张重做
- [ ] 2.1 `POST /api/assets/[assetId]/regenerate`
- [ ] 2.2 UI 加重做按钮 + refinement 输入框
### 风格库
- [ ] 3.1 `src/lib/styles.ts` + 12-16 张 thumbnails`public/styles/`
- [ ] 3.2 `PromptPanel` 改成图卡选择器
### 配件 Vision
- [ ] 4.1 `accessoryDetector.ts` 用 GPT Vision
- [ ] 4.2 配件包改成「识别 → 孤立锚图 → 6 视图 → 组合图」
### 视频和可视化
- [ ] 5.1 视频参考切到宣发主图
- [ ] 6.1 `PackPanel` 加锚图链可视化
---
## 5. 关键文件清单
| 用途 | 路径 |
|---|---|
| GPT provider | `src/lib/providers.ts` |
| 视频 provider | `src/lib/videoProviders.ts` |
| 包生成主逻辑 | `src/lib/packGenerator.ts` |
| 模板定义 | `src/lib/templates.ts` |
| 类型定义 | `src/lib/types.ts` |
| 存储 | `src/lib/storage.ts` |
| 主页 | `src/app/page.tsx` |
| 输入面板 | `src/components/PromptPanel.tsx` |
| 九宫格 | `src/components/ResultGrid.tsx` |
| 资产面板 | `src/components/PackPanel.tsx` |
| 生图 API | `src/app/api/generate/route.ts` |
| 模板查询 API | `src/app/api/templates/route.ts` |
| 角色锁定 API | `src/app/api/character/lock/route.ts` |
| 单包生成 API | `src/app/api/packs/generate/route.ts` |
| 全包生成 API | `src/app/api/packs/generate-all/route.ts` |
| 视频生成 API | `src/app/api/video/generate/route.ts` |
---
## 6. 模型/环境变量约定
- 文本 / 结构化 / Vision`OPENAI_API_KEY` + `GPT_TEXT_MODEL`(默认 `gpt-5.5`
- 图像生成 / 编辑:`OPENAI_API_KEY` + `GPT_IMAGE_MODEL`(默认 `gpt-image-2`edits 端点可能要 `gpt-image-1`,按 OpenAI 实际支持调整)
- 视频:`SEEDANCE_API_KEY` + `SEEDANCE_MODEL`(默认 `seedance-1-0-pro`
**重要**:本项目"文本 / 图片统一走 GPT 最高规格,视频固定 Seedance"是硬约束。不要引入其他供应商。
---
## 7. 验收标准
完成最小可用版本后,应该满足:
1. 选中意向图 → 锁定 → 自动生成 L1 净化锚图
2. 生成专利包,主视图基于 L1其它五视图基于专利主图实际传图不是文本 URL
3. 重做任意一张图UI 显示它的 anchor 是谁,并能单独重做
4. 风格切换有可视化预览
5. 配件包能自动识别玩具上有几个配件,分别生成 6 视图
6. Seedance 视频参考用的是宣发白底图
实测时拿一张复杂玩具图(带帽子、背包、标牌)跑全链路,所有图角色一致、配件清晰、视频与电商图一致。