'use client'; import { useState } from 'react'; import type { AssetPack, AssetTemplate, GenImage, GenSession, PackKind, ToyAsset } from '@/lib/types'; import { PACK_LABELS, PACK_ORDER, PACK_TEMPLATES, TEMPLATE_SLOT_SUMMARY, TEXT_TEMPLATES, VIDEO_TEMPLATES } from '@/lib/templates'; const PACK_DESCRIPTIONS: Record = { patent: '六面视图、45° 立体图、局部放大——外观专利素材', accessories: '配件六视图、连接结构、尺寸和组合关系——独立配件保护 / 打样', production: '尺寸、材料、颜色、拆件、包装——工厂报价 / 打样', marketing: '白底商品图、场景图、细节图、社媒图——新品宣发', }; 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', }, accessories: { ring: 'ring-sky-400/30', chip: 'bg-sky-500/15 text-sky-200 border-sky-400/30', dot: 'bg-sky-400', bar: 'from-sky-400 to-cyan-400', soft: 'from-sky-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, pack, isLoading, onGenerate, }: { kind: PackKind; session: GenSession; primaryImage: GenImage; pack: AssetPack | undefined; 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 === 'accessories' ? 'A' : 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
); })}
); } function SlotOverview() { const stats = [ { label: '总预留位', value: TEMPLATE_SLOT_SUMMARY.totalReservedSlotCount }, { label: '图片/脚本资产位', value: TEMPLATE_SLOT_SUMMARY.imageAssetCount }, { label: '设计说明文字位', value: TEMPLATE_SLOT_SUMMARY.textAssetCount }, { label: '必备图位', value: TEMPLATE_SLOT_SUMMARY.requiredImageAssetCount }, { label: 'Seedance 视频任务', value: TEMPLATE_SLOT_SUMMARY.videoTaskCount }, ]; return (
{stats.map(item => (
{item.value}
{item.label}
))}
); } function TextTemplateSection() { const [showPromptId, setShowPromptId] = useState(null); return (
T

设计说明文字

· {TEXT_TEMPLATES.length} 个文案位

专利说明、工厂打样说明、宣发文案、视频脚本文字包

GPT Text
{TEXT_TEMPLATES.map(template => { const open = showPromptId === template.id; return (
text {template.required && required}
{template.title} {template.kind} {template.outputFormat} {template.required && 必备}

{template.description}

{open && (
                    {template.promptTemplate}
                  
)}
{template.filenamePart}
{template.outputFormat}
待生成
); })}
); } export default function PackPanel({ session, loadingKind, allLoading, characterLoading, videoLoading, onGenerate, onGenerateAll, onLockCharacter, onGenerateVideo, }: { session: GenSession; loadingKind: PackKind | null; allLoading: boolean; characterLoading: boolean; videoLoading: boolean; onGenerate: (image: GenImage, kind: PackKind) => void; onGenerateAll: (image: GenImage) => void; onLockCharacter: (image: GenImage) => void; onGenerateVideo: (image: GenImage, prompt: string) => void; }) { const selectedImages = session.images.filter(image => image.status === 'selected'); const primaryImage = selectedImages[0] ?? null; const packs = session.packs ?? []; if (!primaryImage) { return (
Step · 03 · Lock Character

下一步:资产清单

先在上方九宫格选中一个主方案,下面会展开完整预设资产位(专利 / 生产 / 宣发 / 视频)。

); } return (
{/* Step 03 Header */}
Step · 03 · Lock & Generate

角色锁定 & 资产清单

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

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 Sections */} {PACK_ORDER.map(kind => { const pack = packs.find(p => p.kind === kind && p.sourceImageId === primaryImage.id); return ( onGenerate(primaryImage, kind)} /> ); })} {/* Video Section */}
); }