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.title} + ) : ( +
+
{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 保持一致性。 -

-
-
- selected source -
-
主方案
-
-
- -
- - -
- - {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 && ( -
-
- 已生成 {pack.assets.length} 张 -
- - - - - 下载 manifest - -
- )} -
- ); - })} -
- - {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.title}
-
{asset.view}
-
- {asset.required && ( - - 必备 - - )} -
- ))} -
-
- ); - })} -
- )} - -
-
-
+
+ {/* Step 03 Header */} +
+
-
-
-
Seedance 视频
-
-

- 视频固定走 Seedance · 用当前主方案生成宣发/展示短片 + Step · 03 · Lock & Generate +

角色锁定 & 资产清单

+

+ 以第一个选中图作为主方案。锁定角色设定后,下方按类型分组展示所有预设资产——每项都已固化标题、尺寸、解释和 Prompt,直接选哪一包就生成哪一包。

- 异步任务 +
+ selected source +
+
主方案
+
-
- {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 {