diff --git a/src/api/aiGenerationClient.js b/src/api/aiGenerationClient.js new file mode 100644 index 0000000..29f16fb --- /dev/null +++ b/src/api/aiGenerationClient.js @@ -0,0 +1 @@ +export * from "./aiGenerationClient.ts"; diff --git a/src/api/apiErrorUtils.js b/src/api/apiErrorUtils.js new file mode 100644 index 0000000..45dda37 --- /dev/null +++ b/src/api/apiErrorUtils.js @@ -0,0 +1 @@ +export * from "./apiErrorUtils.ts"; diff --git a/src/api/generationRecordClient.js b/src/api/generationRecordClient.js new file mode 100644 index 0000000..7acc65c --- /dev/null +++ b/src/api/generationRecordClient.js @@ -0,0 +1 @@ +export * from "./generationRecordClient.ts"; diff --git a/src/api/serverConnection.js b/src/api/serverConnection.js new file mode 100644 index 0000000..39e1407 --- /dev/null +++ b/src/api/serverConnection.js @@ -0,0 +1 @@ +export * from "./serverConnection.ts"; diff --git a/src/api/taskSubscription.js b/src/api/taskSubscription.js new file mode 100644 index 0000000..a4e1d36 --- /dev/null +++ b/src/api/taskSubscription.js @@ -0,0 +1 @@ +export * from "./taskSubscription.ts"; diff --git a/src/api/webGenerationGateway.js b/src/api/webGenerationGateway.js new file mode 100644 index 0000000..b58a97f --- /dev/null +++ b/src/api/webGenerationGateway.js @@ -0,0 +1 @@ +export * from "./webGenerationGateway.ts"; diff --git a/src/components/toast/toastStore.js b/src/components/toast/toastStore.js new file mode 100644 index 0000000..0482274 --- /dev/null +++ b/src/components/toast/toastStore.js @@ -0,0 +1 @@ +export * from "./toastStore.ts"; diff --git a/src/data/ossAssets.js b/src/data/ossAssets.js new file mode 100644 index 0000000..8df1f4f --- /dev/null +++ b/src/data/ossAssets.js @@ -0,0 +1 @@ +export * from "./ossAssets.ts"; diff --git a/src/data/workflows.js b/src/data/workflows.js new file mode 100644 index 0000000..fb6a6b7 --- /dev/null +++ b/src/data/workflows.js @@ -0,0 +1 @@ +export * from "./workflows.ts"; diff --git a/src/features/ecommerce/EcommercePage.js b/src/features/ecommerce/EcommercePage.js new file mode 100644 index 0000000..ee7a3b9 --- /dev/null +++ b/src/features/ecommerce/EcommercePage.js @@ -0,0 +1,2 @@ +export { default } from "./EcommercePage.tsx"; +export * from "./EcommercePage.tsx"; diff --git a/src/features/ecommerce/EcommercePage.tsx b/src/features/ecommerce/EcommercePage.tsx index af3f705..d2a0b3d 100644 --- a/src/features/ecommerce/EcommercePage.tsx +++ b/src/features/ecommerce/EcommercePage.tsx @@ -282,6 +282,12 @@ type CloneBasicSelectKey = "platform" | "market" | "language" | "ratio"; type CloneModelSelectKey = "gender" | "age" | "ethnicity" | "body"; type CloneReferenceMode = "upload" | "link"; type CloneReplicateLevelKey = "style" | "high"; +type CloneTemplateAsset = { + id: string; + title: string; + prompt: string; + mediaUrl: string; +}; type TryOnModelSource = "ai" | "library"; type TryOnStatus = "idle" | "modeling" | "ready" | "generating" | "done" | "failed"; type DetailStatus = "idle" | "ready" | "generating" | "done" | "failed"; @@ -1005,6 +1011,112 @@ const productSetOutputOptions: Array<{ key: ProductSetOutputKey; label: string; const cloneOutputOptions: Array<{ key: ProductSetOutputKey; label: string; desc: string; icon: ReactNode }> = [ ...productSetOutputOptions, ]; +const cloneTemplateCards: Record, CloneTemplateAsset[]> = { + set: [ + { + id: "set-main", + title: "商品套图主图", + prompt: "生成一组统一风格的商品套图,包含主图、卖点图、场景图和细节图,主体清晰,色调统一,符合电商平台展示规范。", + mediaUrl: ossAssets.ecommerce.productSet.main, + }, + { + id: "set-scene", + title: "商品套图场景", + prompt: "生成生活化场景商品套图,突出商品在真实环境中的使用感、氛围感和转化卖点。", + mediaUrl: ossAssets.ecommerce.productSet.scene, + }, + { + id: "set-detail", + title: "商品套图细节", + prompt: "生成突出材质、工艺和边缘细节的商品套图,画面干净,信息聚焦,适合电商详情展示。", + mediaUrl: ossAssets.ecommerce.productSet.detail, + }, + { + id: "set-selling", + title: "商品套图卖点", + prompt: "生成强调核心卖点和对比优势的商品套图,信息层级清晰,适合列表页和转化场景。", + mediaUrl: ossAssets.ecommerce.productSet.selling, + }, + ], + detail: [ + { + id: "detail-hero", + title: "详情图头图", + prompt: "生成适用于 A+ 详情页的头图模块,突出品牌感、主卖点和视觉中心,版式清晰高级。", + mediaUrl: ossAssets.ecommerce.detail.longPage, + }, + { + id: "detail-grid-a", + title: "详情图模块 A", + prompt: "生成模块化详情长图,重点展示产品卖点、功能说明和适用场景,适合滚动阅读。", + mediaUrl: ossAssets.ecommerce.detail.gridA, + }, + { + id: "detail-grid-b", + title: "详情图模块 B", + prompt: "生成模块化详情长图,强化材质、规格和使用说明,视觉简洁,信息明确。", + mediaUrl: ossAssets.ecommerce.detail.gridB, + }, + { + id: "detail-grid-c", + title: "详情图模块 C", + prompt: "生成模块化详情页内容,突出品牌叙事、细节拆解和购买理由,保持统一排版。", + mediaUrl: ossAssets.ecommerce.detail.gridC, + }, + ], + model: [ + { + id: "model-dress-a", + title: "模特图穿搭 A", + prompt: "生成真人模特穿搭展示图,突出服装版型、上身效果和整体气质,姿态自然。", + mediaUrl: ossAssets.ecommerce.tryOn.dressA, + }, + { + id: "model-dress-b", + title: "模特图穿搭 B", + prompt: "生成适合商品展示的模特图,强调衣型、垂感和真实穿着效果,画面干净。", + mediaUrl: ossAssets.ecommerce.tryOn.dressB, + }, + { + id: "model-woman", + title: "模特图女模", + prompt: "生成自然站姿的女模特展示图,适合服饰、配件和穿搭类商品展示。", + mediaUrl: ossAssets.ecommerce.tryOn.modelWoman, + }, + { + id: "model-man", + title: "模特图男模", + prompt: "生成真实感更强的男模特展示图,突出上身效果、轮廓和场景氛围。", + mediaUrl: ossAssets.ecommerce.tryOn.modelMan, + }, + ], + video: [ + { + id: "video-hook", + title: "短视频开场", + prompt: "生成适合电商短视频的开场镜头,节奏明确,第一秒就突出产品和核心看点。", + mediaUrl: ossAssets.ecommerce.inspiration.tiktokPreference, + }, + { + id: "video-scene", + title: "短视频场景", + prompt: "生成生活化使用场景的短视频分镜,画面连贯,围绕商品使用过程展开。", + mediaUrl: ossAssets.ecommerce.inspiration.officeStyleSet, + }, + { + id: "video-review", + title: "短视频口播", + prompt: "生成适合口播讲解的电商短视频结构,包含产品亮点、卖点说明和收尾引导。", + mediaUrl: ossAssets.ecommerce.inspiration.asinListing, + }, + { + id: "video-conversion", + title: "短视频转化", + prompt: "生成以转化为目标的短视频分镜,强化开头钩子、卖点展示和行动引导。", + mediaUrl: ossAssets.ecommerce.inspiration.competitorListing, + }, + ], +}; const cloneSetCountOptions: Array<{ key: CloneSetCountKey; title: string; @@ -1457,6 +1569,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const [imageWorkbenchProgress, setImageWorkbenchProgress] = useState(0); const [isProductUploadDragging, setIsProductUploadDragging] = useState(false); const [cloneOutput, setCloneOutput] = useState(defaultCloneOutput); + const [isCloneTemplateStripVisible, setIsCloneTemplateStripVisible] = useState(false); const [videoHistoryVisible, setVideoHistoryVisible] = useState(false); const [isVideoWorkspaceVisible, setIsVideoWorkspaceVisible] = useState(false); const [videoPlanTrigger, setVideoPlanTrigger] = useState(0); @@ -1959,6 +2072,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const selectedProductSetOutput = productSetOutputOptions.find((option) => option.key === productSetOutput) ?? productSetOutputOptions[0]!; const selectedCloneOutput = cloneOutputOptions.find((option) => option.key === cloneOutput) ?? cloneOutputOptions[1]!; + const activeCloneTemplateCards = cloneTemplateCards[cloneOutput === "hot" ? "set" : cloneOutput]; const cloneRequirementPlaceholder = cloneOutput === "model" ? "建议包含以下信息:产品名称、核心卖点、期望场景、模特外貌描述(如小麦色皮肤、齐刘海、眼角有泪痣)、具体参数" @@ -3327,12 +3441,22 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const handleCloneOutputChange = (nextOutput: CloneOutputKey) => { setCloneOutput(nextOutput); + setIsCloneTemplateStripVisible(true); if (nextOutput !== "video") setIsVideoWorkspaceVisible(false); setRatio((current) => normalizeRatioForPlatform(platform, current, nextOutput), ); }; + const handleCloneModeTabClick = (nextOutput: CloneOutputKey) => { + if (nextOutput === cloneOutput) { + setIsCloneTemplateStripVisible((visible) => !visible); + return; + } + handleCloneOutputChange(nextOutput); + setComposerMenu(null); + }; + const handleCloneMarketChange = (nextMarket: string) => { const normalizedMarket = normalizeMarket(nextMarket); setMarket(normalizedMarket); @@ -5171,6 +5295,60 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { toast.success("提示词已填入指令栏"); }; + const applyComposerPrompt = (prompt: string) => { + const nextValue = prompt.slice(0, 500); + setActiveQuickTool(null); + setComposerMenu(null); + setRequirement(nextValue); + syncRequirementMentionQuery(nextValue, nextValue.length); + setInspirationPreview(null); + requestAnimationFrame(() => { + const textarea = requirementTextareaRef.current; + if (textarea) { + textarea.focus(); + textarea.setSelectionRange(nextValue.length, nextValue.length); + textarea.scrollIntoView({ behavior: "smooth", block: "center" }); + } + }); + }; + + const addTemplateImageToComposer = async (card: CloneTemplateAsset) => { + if (productImages.length >= maxCloneProductImages) { + toast.info("模板图片已达上限"); + return; + } + + try { + const stamp = Date.now(); + const uploaded = await aiGenerationClient.uploadAssetByUrl({ + sourceUrl: card.mediaUrl, + name: `${card.id}-${stamp}`, + scope: ecommerceOssScopes.productSource, + }); + const nextImage: CloneImageItem = { + id: `template-${card.id}-${stamp}`, + src: uploaded.url || card.mediaUrl, + name: card.title, + ossKey: uploaded.ossKey, + }; + setProductImages((current) => [...current, nextImage].slice(0, maxCloneProductImages)); + void readImageDimensions(nextImage.src) + .then(({ width, height }) => { + setProductImages((current) => + current.map((item) => (item.id === nextImage.id ? { ...item, width, height } : item)), + ); + }) + .catch(() => undefined); + } catch { + toast.error("模板图片导入失败"); + } + }; + + const handleCloneTemplateCardClick = (card: CloneTemplateAsset) => { + void addTemplateImageToComposer(card); + applyComposerPrompt(card.prompt); + }; + const inspirationPreviewOverlay = inspirationPreview && typeof document !== "undefined" ? createPortal( @@ -5509,7 +5687,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { key={option.key} type="button" className={cloneOutput === option.key ? "is-active" : ""} - onClick={() => handleCloneOutputChange(option.key)} + onClick={() => handleCloneModeTabClick(option.key)} > {option.label} @@ -5608,6 +5786,25 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { {renderComposerMenu()} + {(status === "idle" || status === "ready") && !showMainVideoWorkspace && isCloneTemplateStripVisible ? ( +
+ {activeCloneTemplateCards.map((card) => ( + + ))} +
+ ) : null} {(status === "idle" || status === "ready") && !showMainVideoWorkspace ? (
{[ diff --git a/src/features/ecommerce/ecommerceGenerationPersistence.js b/src/features/ecommerce/ecommerceGenerationPersistence.js new file mode 100644 index 0000000..bfa67cc --- /dev/null +++ b/src/features/ecommerce/ecommerceGenerationPersistence.js @@ -0,0 +1 @@ +export * from "./ecommerceGenerationPersistence.ts"; diff --git a/src/features/ecommerce/ecommerceImageValidation.js b/src/features/ecommerce/ecommerceImageValidation.js new file mode 100644 index 0000000..44cb8f8 --- /dev/null +++ b/src/features/ecommerce/ecommerceImageValidation.js @@ -0,0 +1 @@ +export * from "./ecommerceImageValidation.ts"; diff --git a/src/features/ecommerce/ecommerceTemplates.js b/src/features/ecommerce/ecommerceTemplates.js new file mode 100644 index 0000000..306f354 --- /dev/null +++ b/src/features/ecommerce/ecommerceTemplates.js @@ -0,0 +1 @@ +export * from "./ecommerceTemplates.ts"; diff --git a/src/features/workbench/workbenchDownload.js b/src/features/workbench/workbenchDownload.js new file mode 100644 index 0000000..946a689 --- /dev/null +++ b/src/features/workbench/workbenchDownload.js @@ -0,0 +1 @@ +export * from "./workbenchDownload.ts"; diff --git a/src/hooks/useGenerationTasks.js b/src/hooks/useGenerationTasks.js new file mode 100644 index 0000000..1dfcbe2 --- /dev/null +++ b/src/hooks/useGenerationTasks.js @@ -0,0 +1 @@ +export * from "./useGenerationTasks.ts"; diff --git a/src/hooks/useTypewriter.js b/src/hooks/useTypewriter.js new file mode 100644 index 0000000..7bec054 --- /dev/null +++ b/src/hooks/useTypewriter.js @@ -0,0 +1 @@ +export * from "./useTypewriter.ts"; diff --git a/src/services/backgroundTaskRunner.js b/src/services/backgroundTaskRunner.js new file mode 100644 index 0000000..fc93dc0 --- /dev/null +++ b/src/services/backgroundTaskRunner.js @@ -0,0 +1 @@ +export * from "./backgroundTaskRunner.ts"; diff --git a/src/stores/index.js b/src/stores/index.js new file mode 100644 index 0000000..a88bebe --- /dev/null +++ b/src/stores/index.js @@ -0,0 +1 @@ +export * from "./index.ts"; diff --git a/src/stores/useAppStore.js b/src/stores/useAppStore.js new file mode 100644 index 0000000..1c519f0 --- /dev/null +++ b/src/stores/useAppStore.js @@ -0,0 +1 @@ +export * from "./useAppStore.ts"; diff --git a/src/stores/useGenerationStore.js b/src/stores/useGenerationStore.js new file mode 100644 index 0000000..ea07d36 --- /dev/null +++ b/src/stores/useGenerationStore.js @@ -0,0 +1 @@ +export * from "./useGenerationStore.ts"; diff --git a/src/stores/useProjectStore.js b/src/stores/useProjectStore.js new file mode 100644 index 0000000..45fedf7 --- /dev/null +++ b/src/stores/useProjectStore.js @@ -0,0 +1 @@ +export * from "./useProjectStore.ts"; diff --git a/src/stores/useSessionStore.js b/src/stores/useSessionStore.js new file mode 100644 index 0000000..aea3e1d --- /dev/null +++ b/src/stores/useSessionStore.js @@ -0,0 +1 @@ +export * from "./useSessionStore.ts"; diff --git a/src/stores/useTaskStore.js b/src/stores/useTaskStore.js new file mode 100644 index 0000000..ad35584 --- /dev/null +++ b/src/stores/useTaskStore.js @@ -0,0 +1 @@ +export * from "./useTaskStore.ts"; diff --git a/src/styles/ecommerce-standalone.css b/src/styles/ecommerce-standalone.css index f0b0196..750aa08 100644 --- a/src/styles/ecommerce-standalone.css +++ b/src/styles/ecommerce-standalone.css @@ -9040,6 +9040,56 @@ z-index: 0 !important; } +.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-template-strip { + display: grid !important; + grid-template-columns: repeat(4, minmax(0, 1fr)) !important; + gap: 12px !important; + width: 100% !important; + margin: 14px 0 10px !important; +} + +.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-template-card { + position: relative !important; + display: block !important; + width: 100% !important; + aspect-ratio: 1.46 / 1 !important; + min-height: 92px !important; + padding: 0 !important; + border: 1px solid rgba(30, 189, 219, 0.18) !important; + border-radius: 16px !important; + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(246, 251, 253, 0.98)), + #ffffff !important; + box-shadow: + 0 16px 34px rgba(16, 115, 204, 0.08), + inset 0 1px 0 rgba(255, 255, 255, 0.94) !important; + overflow: hidden !important; + cursor: pointer !important; + transition: transform 180ms ease, border-color 180ms ease, box-shadow 180ms ease, background 180ms ease !important; +} + +.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-template-card:hover { + transform: translateY(-1px) !important; + border-color: rgba(30, 189, 219, 0.34) !important; + box-shadow: + 0 20px 42px rgba(16, 115, 204, 0.11), + 0 0 0 1px rgba(30, 189, 219, 0.12) inset !important; +} + +.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-template-card:focus-visible { + outline: none !important; + border-color: rgba(30, 189, 219, 0.48) !important; + box-shadow: + 0 0 0 3px rgba(30, 189, 219, 0.15), + 0 20px 42px rgba(16, 115, 204, 0.11) !important; +} + +.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-template-card__blank { + display: block !important; + width: 100% !important; + height: 100% !important; +} + .ecommerce-standalone .product-clone-page[data-tool="clone"] .clone-ai-preview { background: #f8f9fa !important; transition: padding-top 520ms cubic-bezier(0.16, 1, 0.3, 1), @@ -9258,6 +9308,10 @@ min-height: 224px !important; } + .ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-template-strip { + grid-template-columns: repeat(2, minmax(0, 1fr)) !important; + } + .ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-quick-board button:not(:nth-child(6n + 1))::before { content: none !important; } diff --git a/src/types.js b/src/types.js new file mode 100644 index 0000000..7ad3e93 --- /dev/null +++ b/src/types.js @@ -0,0 +1 @@ +export * from "./types.ts"; diff --git a/src/utils/enterpriseVideoPolicy.js b/src/utils/enterpriseVideoPolicy.js new file mode 100644 index 0000000..4a79699 --- /dev/null +++ b/src/utils/enterpriseVideoPolicy.js @@ -0,0 +1 @@ +export * from "./enterpriseVideoPolicy.ts"; diff --git a/src/utils/happyHorseRouting.js b/src/utils/happyHorseRouting.js new file mode 100644 index 0000000..df9f521 --- /dev/null +++ b/src/utils/happyHorseRouting.js @@ -0,0 +1 @@ +export * from "./happyHorseRouting.ts"; diff --git a/src/utils/pixverseRouting.js b/src/utils/pixverseRouting.js new file mode 100644 index 0000000..6aeec97 --- /dev/null +++ b/src/utils/pixverseRouting.js @@ -0,0 +1 @@ +export * from "./pixverseRouting.ts"; diff --git a/src/utils/resolveVideoModel.js b/src/utils/resolveVideoModel.js new file mode 100644 index 0000000..0864676 --- /dev/null +++ b/src/utils/resolveVideoModel.js @@ -0,0 +1 @@ +export * from "./resolveVideoModel.ts"; diff --git a/src/utils/taskLifecycle.js b/src/utils/taskLifecycle.js new file mode 100644 index 0000000..585f841 --- /dev/null +++ b/src/utils/taskLifecycle.js @@ -0,0 +1 @@ +export * from "./taskLifecycle.ts"; diff --git a/src/utils/translateTaskError.js b/src/utils/translateTaskError.js new file mode 100644 index 0000000..b761af9 --- /dev/null +++ b/src/utils/translateTaskError.js @@ -0,0 +1 @@ +export * from "./translateTaskError.ts"; diff --git a/src/utils/viduRouting.js b/src/utils/viduRouting.js new file mode 100644 index 0000000..0d63b44 --- /dev/null +++ b/src/utils/viduRouting.js @@ -0,0 +1 @@ +export * from "./viduRouting.ts";