diff --git a/.memory/worklog.json b/.memory/worklog.json
index aef3d34..5a27c99 100644
--- a/.memory/worklog.json
+++ b/.memory/worklog.json
@@ -201,6 +201,25 @@
"type": "session-heartbeat",
"message": "Claude 会话活跃 · 最近命令:claude · 分支 master · 7 项未提交变更 · 最近提交:auto-save 2026-05-19 00:11 (+3, ~1)",
"files_changed": 7
+ },
+ {
+ "ts": "2026-05-19T00:18:42+08:00",
+ "type": "commit",
+ "message": "auto-save 2026-05-19 00:17 (~8)",
+ "hash": "361bbef",
+ "files_changed": 8
+ },
+ {
+ "ts": "2026-05-18T16:23:50Z",
+ "type": "session-heartbeat",
+ "message": "Codex 会话活跃 · 最近命令:codex · 分支 master · 1 项未提交变更 · 最近提交:auto-save 2026-05-19 00:17 (~8)",
+ "files_changed": 1
+ },
+ {
+ "ts": "2026-05-18T16:26:51Z",
+ "type": "session-heartbeat",
+ "message": "Claude 会话活跃 · 最近命令:claude · 分支 master · 1 项未提交变更 · 最近提交:auto-save 2026-05-19 00:17 (~8)",
+ "files_changed": 1
}
]
}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 82b95eb..1af190d 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -201,7 +201,7 @@ export default function Home() {
- {provider === 'gpt' ? 'GPT · 最高规格' : provider === 'mock' ? 'Mock · 占位图' : provider === '?' ? '待连接' : provider}
+ {provider === 'gpt' ? 'GPT · image-2' : provider === 'mock' ? 'Mock · 占位图' : provider === '?' ? '待连接' : provider}
diff --git a/src/components/PackPanel.tsx b/src/components/PackPanel.tsx
index 4a3ce66..7a9555c 100644
--- a/src/components/PackPanel.tsx
+++ b/src/components/PackPanel.tsx
@@ -1,39 +1,293 @@
'use client';
-import type { GenImage, GenSession, PackKind } from '@/lib/types';
-import { PACK_LABELS, PACK_ORDER, VIDEO_TEMPLATES } from '@/lib/templates';
+import { useState } from 'react';
+import type { AssetPack, AssetTemplate, GenImage, GenSession, PackKind, ToyAsset } from '@/lib/types';
+import { PACK_LABELS, PACK_ORDER, PACK_TEMPLATES, VIDEO_TEMPLATES } from '@/lib/templates';
const PACK_DESCRIPTIONS: Record = {
- patent: '六面视图、45° 立体图和局部放大,用于外观专利素材整理。',
- production: '尺寸、材料、颜色、拆件和包装结构,用于工厂报价与打样沟通。',
- marketing: '白底商品图、场景图、细节图和社媒图,用于新品宣发。',
+ patent: '六面视图、45° 立体图、局部放大——外观专利素材',
+ production: '尺寸、材料、颜色、拆件、包装——工厂报价 / 打样',
+ marketing: '白底商品图、场景图、细节图、社媒图——新品宣发',
};
-const PACK_ACCENT: Record = {
+const PACK_ACCENT: Record = {
patent: {
ring: 'ring-violet-400/30',
chip: 'bg-violet-500/15 text-violet-200 border-violet-400/30',
dot: 'bg-violet-400',
bar: 'from-violet-400 to-indigo-400',
+ soft: 'from-violet-500/10 to-transparent',
},
production: {
ring: 'ring-amber-400/30',
chip: 'bg-amber-500/15 text-amber-200 border-amber-400/30',
dot: 'bg-amber-400',
bar: 'from-amber-400 to-orange-400',
+ soft: 'from-amber-500/10 to-transparent',
},
marketing: {
ring: 'ring-emerald-400/30',
chip: 'bg-emerald-500/15 text-emerald-200 border-emerald-400/30',
dot: 'bg-emerald-400',
bar: 'from-emerald-400 to-teal-400',
+ soft: 'from-emerald-500/10 to-transparent',
},
};
+const ASPECT_PX: Record = {
+ '1:1': '1024 × 1024',
+ '3:4': '1024 × 1365',
+ '4:5': '1024 × 1280',
+ '9:16': '1080 × 1920',
+ '16:9': '1920 × 1080',
+ 'long': '1024 × 3200',
+};
+
function manifestUrl(sessionId: string, kind: PackKind, version: string) {
return `/api/export/${sessionId}_${kind}_${version}_manifest.json`;
}
+function AssetRow({
+ template,
+ asset,
+ accent,
+}: {
+ template: AssetTemplate;
+ asset: ToyAsset | undefined;
+ accent: typeof PACK_ACCENT[PackKind];
+}) {
+ const [showPrompt, setShowPrompt] = useState(false);
+ const ready = !!asset;
+ return (
+
+
+ {ready ? (
+

+ ) : (
+
+
{template.view}
+
{template.aspectRatio}
+
+ )}
+ {template.required && (
+
+ 必备
+
+ )}
+
+
+
+
+ {template.title}
+ {template.view}
+ {ready && (
+ ✓ {asset!.version}
+ )}
+
+
{template.description}
+
+ {showPrompt && (
+
+ {template.promptTemplate}
+
+ )}
+
+
+
+
{ASPECT_PX[template.aspectRatio]}
+
{template.aspectRatio}
+
+ {ready ? 'Ready' : '待生成'}
+
+
+
+ );
+}
+
+function PackSection({
+ kind,
+ session,
+ primaryImage,
+ pack,
+ isLoading,
+ onGenerate,
+}: {
+ kind: PackKind;
+ session: GenSession;
+ primaryImage: GenImage;
+ pack: ReturnType[number]> extends never ? never : GenSession['packs'] extends Array ? P : never;
+ isLoading: boolean;
+ onGenerate: () => void;
+}) {
+ const accent = PACK_ACCENT[kind];
+ const templates = PACK_TEMPLATES[kind];
+ const generatedCount = pack?.assets.length ?? 0;
+ const total = templates.length;
+ const progressPct = (generatedCount / total) * 100;
+
+ return (
+
+
+
+
+ {kind === 'patent' ? 'P' : kind === 'production' ? 'F' : 'M'}
+
+
+
+
{PACK_LABELS[kind]}
+ · {total} 张
+
+
{PACK_DESCRIPTIONS[kind]}
+
+
+
+ {pack && (
+
+
+ manifest
+
+ )}
+
+
+
+
+
+
+ 进度
+ {generatedCount} / {total}
+
+
+
+
+
+ {templates.map(template => {
+ const asset = pack?.assets.find(a => a.templateId === template.id);
+ return (
+
+ );
+ })}
+
+
+ );
+}
+
+function VideoSection({
+ videoLoading,
+ primaryImage,
+ onGenerateVideo,
+}: {
+ videoLoading: boolean;
+ primaryImage: GenImage;
+ onGenerateVideo: (image: GenImage, promptTemplate: string) => void;
+}) {
+ const [showPromptId, setShowPromptId] = useState(null);
+ return (
+
+
+
+
V
+
+
+
Seedance 视频
+ · {VIDEO_TEMPLATES.length} 个模板
+
+
异步任务 · 用当前主方案出宣发 / 展示短片
+
+
+
Seedance
+
+
+
+ {VIDEO_TEMPLATES.map(template => {
+ const open = showPromptId === template.id;
+ return (
+
+
+ video
+
+
+
+ {template.title}
+
+ {template.duration}s
+
+
+ {template.ratio}
+
+
+ 1080p
+
+
+
{template.description}
+
+ {open && (
+
+ {template.promptTemplate}
+
+ )}
+
+
+
video
+
+
+
+ );
+ })}
+
+
+ );
+}
+
export default function PackPanel({
session,
loadingKind,
@@ -66,9 +320,9 @@ export default function PackPanel({
⌗
Step · 03 · Lock Character
-
下一步素材包
+
下一步:资产清单
- 先在上方九宫格选中一个主方案,再生成专利、生产、宣发模板包,以及 Seedance 视频。
+ 先在上方九宫格选中一个主方案,下面会展开 35 项预设资产(专利 / 生产 / 宣发 / 视频)。
@@ -77,184 +331,97 @@ export default function PackPanel({
}
return (
-
-
-
-
Step · 03 · Lock Character
-
角色锁定 & 素材包
-
- 以第一个选中图作为主方案。先锁定角色设定,再全量生成三类素材包,所有图引用同一份 CharacterSpec 保持一致性。
-
-
-
-

-
-
主方案
-
-
-
-
-
-
-
-
- {session.characterSpec && (
-
-
-
-
{session.characterSpec.name}
-
{session.characterSpec.oneLiner}
-
-
CharacterSpec · v1
-
-
-
-
形态{session.characterSpec.speciesShape}
-
比例{session.characterSpec.bodyRatio}
-
配色{session.characterSpec.colorPalette.join('、')}
-
材料{session.characterSpec.materials.join('、')}
-
-
- )}
-
-
- {PACK_ORDER.map(kind => {
- const pack = packs.find(item => item.kind === kind && item.sourceImageId === primaryImage.id);
- const isLoading = loadingKind === kind;
- const accent = PACK_ACCENT[kind];
- return (
-
-
-
-
-
-
{PACK_LABELS[kind]}
-
- {pack &&
{pack.version}}
-
-
{PACK_DESCRIPTIONS[kind]}
-
- {pack && (
-
- )}
-
- );
- })}
-
-
- {packs.length > 0 && (
-
- {packs.map(pack => {
- const accent = PACK_ACCENT[pack.kind];
- return (
-
-
-
-
-
- {PACK_LABELS[pack.kind]} · {pack.assets.length} 张
-
-
-
{pack.id}
-
-
- {pack.assets.map(asset => (
-
-
-

-
-
-
{asset.title}
-
{asset.view}
-
- {asset.required && (
-
- 必备
-
- )}
-
- ))}
-
-
- );
- })}
-
- )}
-
-
-
-
+
+ {/* Step 03 Header */}
+
+
-
-
- 视频固定走 Seedance · 用当前主方案生成宣发/展示短片
+ Step · 03 · Lock & Generate
+
角色锁定 & 资产清单
+
+ 以第一个选中图作为主方案。锁定角色设定后,下方按类型分组展示所有预设资产——每项都已固化标题、尺寸、解释和 Prompt,直接选哪一包就生成哪一包。
-
异步任务
+
+

+
+
主方案
+
-
- {VIDEO_TEMPLATES.map(template => (
-
- ))}
+
+
+
+
-
-
+
+ {session.characterSpec && (
+
+
+
+
{session.characterSpec.name}
+
{session.characterSpec.oneLiner}
+
+
CharacterSpec · v1
+
+
+
+
形态{session.characterSpec.speciesShape}
+
比例{session.characterSpec.bodyRatio}
+
配色{session.characterSpec.colorPalette.join('、')}
+
材料{session.characterSpec.materials.join('、')}
+
+
+ )}
+
+
+ {/* Pack Sections */}
+ {PACK_ORDER.map(kind => {
+ const pack = packs.find(p => p.kind === kind && p.sourceImageId === primaryImage.id);
+ return (
+
onGenerate(primaryImage, kind)}
+ />
+ );
+ })}
+
+ {/* Video Section */}
+
+
);
}
diff --git a/src/lib/providers.ts b/src/lib/providers.ts
index 805bc6e..b9b6e1f 100644
--- a/src/lib/providers.ts
+++ b/src/lib/providers.ts
@@ -3,7 +3,7 @@ import type { GenImage } from './types';
export type Provider = 'mock' | 'gpt';
export const GPT_TEXT_MODEL = process.env.GPT_TEXT_MODEL || 'gpt-5.5';
-export const GPT_IMAGE_MODEL = process.env.GPT_IMAGE_MODEL || 'gpt-image-1';
+export const GPT_IMAGE_MODEL = process.env.GPT_IMAGE_MODEL || 'image-2';
const GPT_API_BASE = process.env.GPT_API_BASE || 'https://api.openai.com/v1';
export function detectProvider(): Provider {