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

15 KiB
Raw Blame History

生图链路重构交接文档

给后续 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

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

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 直接参考 L06-30 张图相互之间没有锚定,越生越漂

Gap 4配件链路是假的

位置src/lib/packGenerator.ts:36-37

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

imageUrl: image.url  // 还是那张原始意向图

问题:视频里的玩具和已定稿的电商图长得不一样。

正确做法:视频参考宣发白底主图(mkt_white_front),如果未生成就退到专利主图。


3. 实施方案(按依赖顺序)

阶段 1真图生图链路最高优先级1+2+3+5 是最小可用版本)

1.1 新增 generateGptImageEditproviders.ts

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-dataimage (Buffer)、promptmodel=gpt-image-1size
  • 必须传图字节而不是 URL
  • 返回 b64_json → 解码为 data URL

保留现有 generateGptImages 用于 L0 意向图阶段(无锚图,纯文本生图)。

1.2 数据模型扩展types.ts

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

{ 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
  • generateGptImageEditprompt = "保持角色完全一致,把背景换成纯白色,产品居中,无任何文字水印,光线均匀"
  • 输出图 URL 写回 session.characterSpec.cleanReferenceImageUrl

1.5 改造 generateAssetPackpackGenerator.ts

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
  • generateGptImageEditprompt 末尾追加 userRefinement
  • 替换原 asset保留 id 不变
  • 更新 session JSON

2.2 UI

PackPanel.tsx 里每个 AssetRow 加"重做"按钮和"refinement"输入框。

阶段 3风格库可视化

3.1 新增 src/lib/styles.ts

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

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 组合)

数据模型加:

export type AccessoryGroup = {
  id: string;
  name: string;
  isolatedAnchorUrl: string;
  views: ToyAsset[];  // 6+ 视图
};

export type AssetPack = {
  // ...existing
  accessoryGroups?: AccessoryGroup[];  // 仅 kind === 'accessories' 用
};

阶段 5视频参考一致性

5.1 改 handleGenerateVideopage.tsx

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 新增 generateGptImageEditmultipart 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 张 thumbnailspublic/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. 模型/环境变量约定

  • 文本 / 结构化 / VisionOPENAI_API_KEY + GPT_TEXT_MODEL(默认 gpt-5.5
  • 图像生成 / 编辑:OPENAI_API_KEY + GPT_IMAGE_MODEL(默认 gpt-image-2edits 端点可能要 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 视频参考用的是宣发白底图

实测时拿一张复杂玩具图(带帽子、背包、标牌)跑全链路,所有图角色一致、配件清晰、视频与电商图一致。