@@ -65,8 +65,10 @@ import {
getPlatformDefaultRatio ,
getPlatformLanguageOptions ,
getPlatformRatioOptions ,
languageOptions ,
marketLanguageOptions ,
marketOptions ,
normalizeLanguage ,
normalizeLanguageForPlatform ,
normalizeMarket ,
normalizePlatform ,
@@ -167,6 +169,20 @@ type SmartCutoutImageItem = { src: string; name: string; originalSrc?: string };
const ecommerceInspirationTabs = [ "最近打开" , "一键同款" , "海报模板" , "热门" , "商品图" , "模特穿戴" ] ;
const ecommerceInspirationAssets = ossAssets . ecommerce . inspiration ;
const getMarketsForLanguage = ( languageValue : string ) = > {
const normalizedLanguage = normalizeLanguage ( languageValue ) ;
const matches = marketLanguageOptions
. filter ( ( option ) = > option . languages . some ( ( item ) = > normalizeLanguage ( item ) === normalizedLanguage ) )
. map ( ( option ) = > option . country ) ;
return matches . length ? matches : marketOptions ;
} ;
const normalizeMarketForLanguage = ( marketValue : string , languageValue : string ) = > {
const normalizedMarket = normalizeMarket ( marketValue ) ;
const languageMarkets = getMarketsForLanguage ( languageValue ) ;
return languageMarkets . includes ( normalizedMarket ) ? normalizedMarket : ( languageMarkets [ 0 ] ? ? marketOptions [ 0 ] ? ? normalizedMarket ) ;
} ;
const ecommerceInspirationRows = [
{
title : "作品记录" ,
@@ -313,6 +329,20 @@ interface CanvasNode {
y : number ;
}
interface RecordCanvasGroup {
id : string ;
mode : string ;
outputLabel : string ;
settingLabel : string ;
sourceImages : CloneImageItem [ ] ;
results : CloneResult [ ] ;
createdAt : number ;
turnIndex : number ;
status : EcommerceHistoryStatus ;
x : number ;
y : number ;
}
interface PreviewTouchPoint {
id : number ;
x : number ;
@@ -341,9 +371,6 @@ interface EcommerceImagePromptOptions {
}
const sideTools : Array < { key : ProductKitToolKey ; label : string ; icon : ReactNode } > = [
{ key : "set" , label : "商品套图" , icon : < AppstoreOutlined / > } ,
{ key : "detail" , label : "A+详情" , icon : < FileImageOutlined / > } ,
{ key : "wear" , label : "服饰穿搭" , icon : < SkinOutlined / > } ,
{ key : "clone" , label : "电商AI作图" , icon : < AppstoreOutlined / > } ,
] ;
@@ -1185,6 +1212,18 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const skipInitialCloneAutoSaveRef = useRef ( true ) ;
const skipNextCloneAutoSaveRef = useRef ( false ) ;
const [ activeTool , setActiveTool ] = useState < ProductKitToolKey > ( "clone" ) ;
useEffect ( ( ) = > {
if ( activeTool === "set" ) {
setActiveTool ( "clone" ) ;
setActiveQuickTool ( "quick-set" ) ;
} else if ( activeTool === "detail" ) {
setActiveTool ( "clone" ) ;
setActiveQuickTool ( "detail" ) ;
} else if ( activeTool === "wear" ) {
setActiveTool ( "clone" ) ;
setActiveQuickTool ( null ) ;
}
} , [ activeTool ] ) ;
useEffect ( ( ) = > {
setPreviewZoom ( 1 ) ;
setIsCommandComposerCompact ( false ) ;
@@ -1730,7 +1769,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const [ detailProgress , setDetailProgress ] = useState ( 0 ) ;
const [ hotRequirement , setHotRequirement ] = useState ( "" ) ;
const [ isHotMaterialDragging , setIsHotMaterialDragging ] = useState ( false ) ;
const [ hotMaterialHoverZoom , setHotMaterialHoverZoom ] = useState < { src : string ; x : number ; y : number ; placement : "above " | "below " } | null > ( null ) ;
const [ hotMaterialHoverZoom , setHotMaterialHoverZoom ] = useState < { src : string ; x : number ; y : number ; placement : "right " | "left " } | null > ( null ) ;
const [ hotPlatform , setHotPlatform ] = useState ( platformOptions [ 0 ] ) ;
const [ hotMarket , setHotMarket ] = useState ( marketOptions [ 0 ] ) ;
const [ hotLanguage , setHotLanguage ] = useState ( getPlatformDefaultLanguage ( platformOptions [ 0 ] , marketOptions [ 0 ] ) ) ;
@@ -1793,6 +1832,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
( ) = > getPlatformLanguageOptions ( hotPlatform , hotMarket ) ,
[ hotMarket , hotPlatform ] ,
) ;
const languageMarketOptions = languageOptions ;
const cloneMarketOptions = useMemo ( ( ) = > getMarketsForLanguage ( language ) , [ language ] ) ;
const detailMarketOptions = useMemo ( ( ) = > getMarketsForLanguage ( detailLanguage ) , [ detailLanguage ] ) ;
const hotMarketOptions = useMemo ( ( ) = > getMarketsForLanguage ( hotLanguage ) , [ hotLanguage ] ) ;
const ecommerceMentionImages : MentionImageOption [ ] = [
. . . productImages . map ( ( image , index ) = > ( { . . . image , label : ` 商品图 ${ index + 1 } ` } ) ) ,
. . . cloneReferenceImages . map ( ( image , index ) = > ( { . . . image , label : ` 参考图 ${ index + 1 } ` } ) ) ,
@@ -1807,6 +1850,43 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
[ productImages ] ,
) ;
const quickPageSidebarItems : Array < { key : NonNullable < typeof activeQuickTool > ; label : string ; icon : ReactNode } > = [
{ key : "quick-set" , label : "商品套图" , icon : < AppstoreAddOutlined / > } ,
{ key : "detail" , label : "A+详情" , icon : < LayoutOutlined / > } ,
{ key : "hot" , label : "爆款复刻" , icon : < FireOutlined / > } ,
{ key : "oneClickVideo" , label : "一键视频" , icon : < PlayCircleOutlined / > } ,
{ key : "image-edit" , label : "图片修改" , icon : < HighlightOutlined / > } ,
{ key : "watermark" , label : "去除水印" , icon : < ClearOutlined / > } ,
{ key : "copywriting" , label : "一键文案" , icon : < FileTextOutlined / > } ,
{ key : "translate" , label : "图片翻译" , icon : < TranslationOutlined / > } ,
] ;
const commerceQuickPageSidebarItems = quickPageSidebarItems . filter ( ( item ) = >
[ "quick-set" , "detail" , "hot" , "oneClickVideo" ] . includes ( item . key ) ,
) ;
const visualQuickPageSidebarItems = quickPageSidebarItems . filter ( ( item ) = >
[ "image-edit" , "watermark" , "copywriting" , "translate" ] . includes ( item . key ) ,
) ;
const renderQuickPageSidebar = (
activeKey : NonNullable < typeof activeQuickTool > ,
group : "commerce" | "visual" = "commerce" ,
) = > (
< nav className = "ecom-quick-page-sidebar" aria-label = "快捷工具切换" >
{ ( group === "visual" ? visualQuickPageSidebarItems : commerceQuickPageSidebarItems ) . map ( ( item ) = > (
< button
key = { item . key }
type = "button"
className = { item . key === activeKey ? "is-active" : "" }
onClick = { ( ) = > setActiveQuickTool ( item . key ) }
>
{ item . icon }
< span > { item . label } < / span >
< / button >
) ) }
< / nav >
) ;
const selectedProductSetOutput =
productSetOutputOptions . find ( ( option ) = > option . key === productSetOutput ) ? ? productSetOutputOptions [ 0 ] ! ;
const selectedCloneOutput = cloneOutputOptions . find ( ( option ) = > option . key === cloneOutput ) ? ? cloneOutputOptions [ 1 ] ! ;
@@ -2209,8 +2289,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const openImageTranslatePage = ( ) = > {
clearSmartCutoutTransition ( ) ;
setActiveQuickTool ( "translate" ) ;
setComposerMenu ( null ) ;
toast . info ( "功能正在优化中" ) ;
} ;
const closeImageTranslatePage = ( ) = > {
@@ -3255,7 +3335,6 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
setRatio ( ( current ) = >
normalizeRatioForPlatform ( normalizedPlatform , current , cloneOutput ) ,
) ;
setLanguage ( getPlatformDefaultLanguage ( normalizedPlatform , market ) ) ;
} ;
const handleCloneOutputChange = ( nextOutput : CloneOutputKey ) = > {
@@ -3305,10 +3384,15 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
setLanguage ( getPlatformDefaultLanguage ( platform , normalizedMarket ) ) ;
} ;
const handleCloneLanguageChange = ( nextLanguage : string ) = > {
const normalizedLanguage = normalizeLanguage ( nextLanguage ) ;
setLanguage ( normalizedLanguage ) ;
setMarket ( ( current ) = > normalizeMarketForLanguage ( current , normalizedLanguage ) ) ;
} ;
const handleDetailPlatformChange = ( nextPlatform : string ) = > {
const normalizedPlatform = normalizePlatform ( nextPlatform ) ;
setDetailPlatform ( normalizedPlatform ) ;
setDetailLanguage ( getPlatformDefaultLanguage ( normalizedPlatform , detailMarket ) ) ;
setDetailRatio ( ( current ) = > getQuickSetRatioValue ( current ) ) ;
} ;
@@ -3318,6 +3402,12 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
setDetailLanguage ( getPlatformDefaultLanguage ( detailPlatform , normalizedMarket ) ) ;
} ;
const handleDetailLanguageChange = ( nextLanguage : string ) = > {
const normalizedLanguage = normalizeLanguage ( nextLanguage ) ;
setDetailLanguage ( normalizedLanguage ) ;
setDetailMarket ( ( current ) = > normalizeMarketForLanguage ( current , normalizedLanguage ) ) ;
} ;
const createCloneSettingSnapshot = ( name : string , id = ` clone-setting- ${ Date . now ( ) } ` ) : CloneSavedSetting = > ( {
id ,
name ,
@@ -4462,7 +4552,6 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const handleHotPlatformChange = ( nextPlatform : string ) = > {
const normalizedPlatform = normalizePlatform ( nextPlatform ) ;
setHotPlatform ( normalizedPlatform ) ;
setHotLanguage ( getPlatformDefaultLanguage ( normalizedPlatform , hotMarket ) ) ;
setHotRatio ( ( current ) = > getQuickSetRatioValue ( current ) ) ;
} ;
@@ -4472,6 +4561,12 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
setHotLanguage ( getPlatformDefaultLanguage ( hotPlatform , normalizedMarket ) ) ;
} ;
const handleHotLanguageChange = ( nextLanguage : string ) = > {
const normalizedLanguage = normalizeLanguage ( nextLanguage ) ;
setHotLanguage ( normalizedLanguage ) ;
setHotMarket ( ( current ) = > normalizeMarketForLanguage ( current , normalizedLanguage ) ) ;
} ;
const handleHotAiWrite = ( ) = > {
setHotRequirement (
"1.产品名称:便携式咖啡保温杯\n2.核心卖点:316不锈钢内胆、12小时长效保温、防漏便携、大容量\n3.参考风格:极简日系、暖光氛围、生活场景\n4.期望场景:办公桌面、户外通勤、运动健身\n5.具体参数:容量500ml、口径4.5cm、高度22cm" ,
@@ -4587,20 +4682,19 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const handleHotMaterialMouseEnter = ( src : string , event : ReactMouseEvent < HTMLElement > ) = > {
const rect = event . currentTarget . getBoundingClientRect ( ) ;
const previewHalfWidth = 150 ;
const previewHeight = 360 ;
const previewWidth = 300 ;
const previewHeight = 190 ;
const gap = 12 ;
const viewportWidth = window . innerWidth || document . documentElement . clientWidth ;
const viewportHeight = window . innerHeight || document . documentElement . clientHeight ;
const x = Math . min (
Math . max ( rect . left + rect . width / 2 , previewHalfWidth + gap ) ,
Math . max ( previewHalfWidth + gap , viewportWidth - previewHalfWidth - gap ) ,
const canShowRight = rect . right + gap + previewWidth <= viewportWidth - gap ;
const placement : "right" | "left" = canShowRight ? "right" : "left" ;
const x = placement === "right" ? rect . right + gap : Math.max ( gap , rect . left - gap ) ;
const y = Math . min (
Math . max ( rect . top + rect . height / 2 , previewHeight / 2 + gap ) ,
Math . max ( previewHeight / 2 + gap , viewportHeight - previewHeight / 2 - gap ) ,
) ;
const showAbove = rect . top > previewHeight + gap ;
const y = showAbove
? rect . top - gap
: Math . min ( rect . bottom + gap , viewportHeight - gap ) ;
setHotMaterialHoverZoom ( { src , x , y , placement : showAbove ? "above" : "below" } ) ;
setHotMaterialHoverZoom ( { src , x , y , placement } ) ;
} ;
const handleHotMaterialMouseLeave = ( ) = > setHotMaterialHoverZoom ( null ) ;
@@ -4624,13 +4718,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
onRemove ( item . id ) ;
} }
>
< svg viewBox = "0 0 24 24" aria-hidden = "true" focusable = "false" >
< path d = "M9 6V5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v1" / >
< path d = "M5 6h14" / >
< path d = "M8 6l1 14h6l1-14" / >
< path d = "M10.5 10v6" / >
< path d = "M13.5 10v6" / >
< / svg >
脳
< / button >
< / figure >
) ) }
@@ -4779,6 +4867,65 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
isOneClickVideoTool ||
isVideoWorkspaceVisible ||
Boolean ( activeHistoryRecordId ) ;
const isMainCloneWorkspace = isCloneTool && ! isSmartCutoutTool && ! isQuickDetailTool && ! isWatermarkTool && ! isTranslateTool && ! isImageEditTool && ! isQuickSetTool && ! isCopywritingTool && ! isOneClickVideoTool ;
const isRecordDetailWorkspace = isMainCloneWorkspace && Boolean ( activeHistoryRecordId ) ;
const activeHistoryRecord = activeHistoryRecordId ? ecommerceHistoryRecords . find ( ( record ) = > record . id === activeHistoryRecordId ) : null ;
const activeConversationTurns = activeHistoryRecord
? activeHistoryRecord . turns ? . length
? activeHistoryRecord . turns
: [ buildHistoryTurnFromRecord ( activeHistoryRecord ) ]
: [ ] ;
const activeRecallTurn = activeConversationTurns [ activeConversationTurns . length - 1 ] ;
const commandRecallPrompt =
requirement . trim ( ) ||
activeRecallTurn ? . requirement ? . trim ( ) ||
activeHistoryRecord ? . requirement ? . trim ( ) ||
"展开完整对话,继续补充生成需求。" ;
const commandRecallAsset = productImages [ 0 ] || activeRecallTurn ? . productImages ? . [ 0 ] || activeHistoryRecord ? . productImages ? . [ 0 ] ;
const commandRecallStatus =
status === "generating"
? "生成中"
: activeRecallTurn ? . status === "failed" || status === "failed"
? "可恢复"
: "继续生成" ;
const hasGeneratedCloneWork = status === "done" || canvasNodes . length > 0 ;
const shouldUseCompactComposer = isCommandComposerCompact && hasGeneratedCloneWork && ! ( isRecordDetailWorkspace && ! isCloneConversationCollapsed ) ;
const shouldShowScenarioScrollHint = ! isRecordDetailWorkspace ;
const getHistoryTurnSettingLabel = ( turn : EcommerceHistoryTurn ) = > {
if ( turn . settingLabel ) return turn . settingLabel ;
if ( turn . output === "set" && turn . results ? . length && ! turn . setResultImages ? . length ) {
return ` 单图 ${ turn . results . length } 张 ` ;
}
if ( turn . output === "set" ) {
const total = cloneSetCountKeys . reduce ( ( sum , key ) = > sum + ( turn . setCounts ? . [ key ] ? ? 0 ) , 0 ) ;
return ` 套图 ${ total || 1 } 张 ` ;
}
if ( turn . output === "detail" ) return ` 详情 ${ turn . detailModules ? . length || 1 } 项 ` ;
if ( turn . output === "model" ) return ` 模特 ${ turn . modelScenes ? . length || 1 } 景 ` ;
return cloneOutputOptions . find ( ( option ) = > option . key === turn . output ) ? . label || selectedCloneOutput . label ;
} ;
const recordCanvasGroups : RecordCanvasGroup [ ] = isRecordDetailWorkspace
? activeConversationTurns . reduce < RecordCanvasGroup [ ] > ( ( groups , turn , index ) = > {
const turnResults = getTurnResults ( turn ) ;
if ( ! turnResults . length && turn . status !== "generating" ) return groups ;
const canvasNode = canvasNodes . find ( ( node ) = > node . id === turn . id ) ;
const outputLabel = turn . modeLabel || cloneOutputOptions . find ( ( option ) = > option . key === turn . output ) ? . label || selectedCloneOutput . label ;
groups . push ( {
id : turn.id ,
mode : turn.output ,
outputLabel ,
settingLabel : getHistoryTurnSettingLabel ( turn ) ,
sourceImages : turn.productImages ,
results : turnResults ,
createdAt : turn.createdAt ,
turnIndex : index + 1 ,
status : turn.status ,
x : canvasNode?.x ? ? groups . length * 420 ,
y : canvasNode?.y ? ? ( groups . length % 2 === 0 ? 0 : 160 ) ,
} ) ;
return groups ;
} , [ ] )
: [ ] ;
useEffect ( ( ) = > {
onWorkspaceChromeChange ? . ( { isToolPage : isWorkspaceToolPage } ) ;
@@ -5257,8 +5404,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
onChange : ( value : string ) = > void ;
} > = [
{ key : "platform" , label : "平台" , value : platform , options : platformOptions , onChange : handleClonePlatformChange } ,
{ key : "market" , label : "国家" , value : market , options : marketOptions , onChange : handleCloneMarketChange } ,
{ key : "language" , label : "语种" , value : language , options : cloneLanguageOptions , onChange : setLanguage } ,
{ key : "market" , label : "国家" , value : market , options : cloneMarketOptions , onChange : handleCloneMarketChange } ,
{ key : "language" , label : "语种" , value : language , options : languageMarketOptions , onChange : handleCloneLanguageChange } ,
{ key : "ratio" , label : "尺寸/比例" , value : ratio , options : cloneRatioOptions , onChange : setRatio } ,
] ;
const quickDetailBasicSelects : Array < {
@@ -5269,8 +5416,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
onChange : ( value : string ) = > void ;
} > = [
{ key : "platform" , label : "平台" , value : detailPlatform , options : platformOptions , onChange : handleDetailPlatformChange } ,
{ key : "market" , label : "国家" , value : detailMarket , options : marketOptions , onChange : handleDetailMarketChange } ,
{ key : "language" , label : "语种" , value : detailLanguage , options : detailLanguageOptions , onChange : setDetailLanguage } ,
{ key : "market" , label : "国家" , value : detailMarket , options : detailMarketOptions , onChange : handleDetailMarketChange } ,
{ key : "language" , label : "语种" , value : detailLanguage , options : languageMarketOptions , onChange : handleDetailLanguageChange } ,
{ key : "ratio" , label : "尺寸/比例" , value : getQuickSetRatioValue ( detailRatio ) , options : quickSetRatioOptions , onChange : setDetailRatio } ,
] ;
@@ -5282,8 +5429,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
onChange : ( value : string ) = > void ;
} > = [
{ key : "platform" , label : "平台" , value : hotPlatform , options : platformOptions , onChange : handleHotPlatformChange } ,
{ key : "market" , label : "国家" , value : hotMarket , options : marketOptions , onChange : handleHotMarketChange } ,
{ key : "language" , label : "语种" , value : hotLanguage , options : hotLanguageOptions , onChange : setHotLanguage } ,
{ key : "market" , label : "国家" , value : hotMarket , options : hotMarketOptions , onChange : handleHotMarketChange } ,
{ key : "language" , label : "语种" , value : hotLanguage , options : languageMarketOptions , onChange : handleHotLanguageChange } ,
{ key : "ratio" , label : "尺寸/比例" , value : getQuickSetRatioValue ( hotRatio ) , options : quickSetRatioOptions , onChange : setHotRatio } ,
] ;
@@ -5295,8 +5442,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
onChange : ( value : string ) = > void ;
} > = [
{ key : "platform" , label : "平台" , value : platform , options : platformOptions , onChange : setPlatform } ,
{ key : "market" , label : "国家" , value : market , options : marketOptions , onChange : setMarket } ,
{ key : "language" , label : "语种" , value : language , options : cloneLanguageOptions , onChange : setLanguage } ,
{ key : "market" , label : "国家" , value : market , options : cloneMarketOptions , onChange : handleCloneMarketChange } ,
{ key : "language" , label : "语种" , value : language , options : languageMarketOptions , onChange : handleCloneLanguageChange } ,
{ key : "ratio" , label : "尺寸/比例" , value : getQuickSetRatioValue ( ratio ) , options : quickSetRatioOptions , onChange : setRatio } ,
] ;
@@ -6229,93 +6376,211 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
)
) : (
< >
{ status === "done" || canvasNodes . length > 0 ? (
{ status === "done" || canvasNodes . length > 0 || ( isRecordDetailWorkspace && recordCanvasGroups . length > 0 ) ? (
< div className = "clone-ai-preview-zoom-wrap" style = { previewTransformStyle } >
< section className = "clone-ai-canvas-nodes" aria-label = "生成结果" >
{ canvasNodes . map ( ( node ) = > (
< article
key = { node . id }
className = "clone-ai-canvas-node"
data - mode = { node . mode }
data - node - id = { node . id }
style = { { transform : ` translate( ${ node . x } px, ${ node . y } px) ` } }
onPointerDown = { ( event ) = > startCanvasNodeDrag ( event , node ) }
onPointerMove = { ( event ) = > moveCanvasNodeDrag ( event , node . id ) }
onPointerUp = { ( event ) = > stopCanvasNodeDrag ( event , node . id ) }
onPointerCancel = { ( event ) = > stopCanvasNodeDrag ( event , node . id ) }
>
< div
className = "clone-ai-node-drag-handle"
onPointerDown = { ( e ) = > {
if ( e . button !== 0 ) return ;
e . stopPropagation ( ) ;
e . currentTarget . setPointerCapture ( e . pointerId ) ;
nodeDragRef . current = { active : true , nodeId : node.id , startX : e.clientX , startY : e.clientY , originX : node.x , originY : node.y } ;
} }
onPointerMove = { ( e ) = > {
const drag = nodeDragRef . current ;
if ( ! drag . active || drag . nodeId !== node . id ) return ;
const zoom = previewZoomRef . current ;
const dx = ( e . clientX - drag . startX ) / zoom ;
const dy = ( e . clientY - drag . startY ) / zoom ;
setCanvasNodes ( ( prev ) = > prev . map ( ( n ) = > n . id === node . id ? { . . . n , x : drag.originX + dx , y : drag.originY + dy } : n ) ) ;
} }
onPointerUp = { ( e ) = > {
if ( nodeDragRef . current . nodeId === node . id ) {
nodeDragRef . current = { . . . nodeDragRef . current , active : false } ;
e . currentTarget . releasePointerCapture ( e . pointerId ) ;
}
} }
/ >
< div className = "clone-ai-source-stack" >
< button
type = "button"
className = "clone-ai-source-corner-action"
onClick = { node . sourceImage ? ( ) = > openProductSetPreview ( { src : node.sourceImage ! , label : "原图素材" } ) : undefined }
disabled = { ! node . sourceImage }
>
原 图 素 材
< / button >
< button
type = "button"
className = "clone-ai-main-result"
aria - label = "预览原图素材"
onClick = { node . sourceImage ? ( ) = > openProductSetPreview ( { src : node.sourceImage ! , label : "原图素材" } ) : undefined }
disabled = { ! node . sourceImage }
>
{ node . sourceImage ? (
< img
src = { node . sourceImage }
alt = "原图素材"
onError = { ( event ) = > {
event . currentTarget . style . display = "none" ;
event . currentTarget . parentElement ? . classList . add ( "is-missing-source" ) ;
{ isRecordDetailWorkspace ? (
< section className = "clone-ai-turn-groups" aria-label = "按轮次生成结果" >
{ recordCanvasGroups . map ( ( group ) = > {
const primarySource = group . sourceImages [ 0 ] ;
const dragNode : CanvasNode = {
id : group.id ,
mode : group.mode ,
sourceImage : primarySource?.src ,
results : group.results ,
createdAt : group.createdAt ,
x : group.x ,
y : group.y ,
} ;
return (
< article
key = { group . id }
className = { ` clone-ai-canvas-node clone-ai-turn-group is- ${ group . status } ` }
data - mode = { group . mode }
data - turn - id = { group . id }
data - node - id = { group . id }
style = { { transform : ` translate( ${ group . x } px, ${ group . y } px) ` } }
onPointerDown = { ( event ) = > startCanvasNodeDrag ( event , dragNode ) }
onPointerMove = { ( event ) = > moveCanvasNodeDrag ( event , group . id ) }
onPointerUp = { ( event ) = > stopCanvasNodeDrag ( event , group . id ) }
onPointerCancel = { ( event ) = > stopCanvasNodeDrag ( event , group . id ) }
>
< div
className = "clone-ai-node-drag-handle"
onPointerDown = { ( e ) = > {
if ( e . button !== 0 ) return ;
e . stopPropagation ( ) ;
e . currentTarget . setPointerCapture ( e . pointerId ) ;
nodeDragRef . current = { active : true , nodeId : group.id , startX : e.clientX , startY : e.clientY , originX : group.x , originY : group.y } ;
} }
onPointerMove = { ( e ) = > {
const drag = nodeDragRef . current ;
if ( ! drag . active || drag . nodeId !== group . id ) return ;
const zoom = previewZoomRef . current ;
const dx = ( e . clientX - drag . startX ) / zoom ;
const dy = ( e . clientY - drag . startY ) / zoom ;
setCanvasNodes ( ( prev ) = > prev . map ( ( n ) = > n . id === group . id ? { . . . n , x : drag.originX + dx , y : drag.originY + dy } : n ) ) ;
} }
onPointerUp = { ( e ) = > {
if ( nodeDragRef . current . nodeId === group . id ) {
nodeDragRef . current = { . . . nodeDragRef . current , active : false } ;
e . currentTarget . releasePointerCapture ( e . pointerId ) ;
}
} }
/ >
) : null }
< span className = "clone-ai-source-missing" > 素 材 不 可 用 < / span >
< / button >
< / div >
< div className = "clone-ai-flow-arrow" aria-hidden = "true" / >
< div className = "clone-ai-result-stack" >
< span className = "clone-ai-node-label" > { node . mode === "set" ? "套图" : node . mode === "detail" ? "详情图" : node . mode == = "model" ? "模特图" : node . mode === "hot" ? "爆款图" : node . mode } < / span >
< div className = "clone-ai-result-grid result-reveal" >
{ node . results . map ( ( card ) = > (
< button key = { card . id } type = "button" style = { { aspectRatio : parseRatioToAspectCss ( ratio ) } } onClick = { ( ) = > openProductSetPreview ( card , { nodeId : node.id , removable : true } ) } >
< img src = { card . src } alt = { card . label } / >
< div className = { ` clone-ai-turn-group__body ${ primarySource ? . src ? "" : " is-without-source" } ` } >
{ primarySource ? . src ? (
< >
< div className = "clone-ai-source-stack" >
< button
type = "button"
className = "clone-ai-source-corner-action"
onClick = { ( ) = > openProductSetPreview ( { src : primarySource.src , label : "原图素材" } ) }
>
原 图 素 材
< / button >
< button
type = "button"
className = "clone-ai-main-result"
aria - label = "预览原图素材"
onClick = { ( ) = > openProductSetPreview ( { src : primarySource.src , label : "原图素材" } ) }
>
< img
src = { primarySource . src }
alt = { primarySource . name || "原图素材" }
onError = { ( event ) = > {
event . currentTarget . style . display = "none" ;
event . currentTarget . parentElement ? . classList . add ( "is-missing-source" ) ;
} }
/ >
< span className = "clone-ai-source-missing" > 素 材 不 可 用 < / span >
< / button >
< / div >
< div className = "clone-ai-flow-arrow" aria-hidden = "true" / >
< / >
) : null }
< div className = "clone-ai-result-stack" >
< span className = "clone-ai-node-label" > { group . outputLabel } < / span >
< div className = "clone-ai-result-grid result-reveal" >
{ group . results . map ( ( card ) = > (
< button key = { card . id } type = "button" style = { { aspectRatio : parseRatioToAspectCss ( ratio ) } } onClick = { ( ) = > openProductSetPreview ( card , { nodeId : group.id , removable : true } ) } >
< img src = { card . src } alt = { card . label } / >
< / button >
) ) }
{ group . status === "generating" && ! group . results . length ? (
< div className = "clone-ai-turn-group__pending" aria-live = "polite" >
< LoadingOutlined / >
< span > 正 在 生 成 { group . outputLabel } . . . < / span >
< / div >
) : null }
< / div >
< / div >
< / div >
< / article >
) ;
} ) }
{ status === "generating" && ! recordCanvasGroups . some ( ( group ) = > group . status === "generating" ) ? (
< article className = "clone-ai-turn-group is-generating" >
< header className = "clone-ai-turn-group__head" >
< div >
< span > 继 续 生 成 < / span >
< strong > { selectedCloneOutput . label } < / strong >
< / div >
< small > AI 正 在 整 理 新 一 轮 结 果 < / small >
< / header >
< div className = "clone-ai-turn-group__pending" aria-live = "polite" >
< LoadingOutlined / >
< span > 正 在 生 成 { selectedCloneOutput . label } . . . < / span >
< / div >
< / article >
) : null }
< / section >
) : (
< section className = "clone-ai-canvas-nodes" aria-label = "生成结果" >
{ canvasNodes . map ( ( node ) = > (
< article
key = { node . id }
className = "clone-ai-canvas-node"
data - mode = { node . mode }
data - node - id = { node . id }
style = { { transform : ` translate( ${ node . x } px, ${ node . y } px) ` } }
onPointerDown = { ( event ) = > startCanvasNodeDrag ( event , node ) }
onPointerMove = { ( event ) = > moveCanvasNodeDrag ( event , node . id ) }
onPointerUp = { ( event ) = > stopCanvasNodeDrag ( event , node . id ) }
onPointerCancel = { ( event ) = > stopCanvasNodeDrag ( event , node . id ) }
>
< div
className = "clone-ai-node-drag-handle"
onPointerDown = { ( e ) = > {
if ( e . button !== 0 ) return ;
e . stopPropagation ( ) ;
e . currentTarget . setPointerCapture ( e . pointerId ) ;
nodeDragRef . current = { active : true , nodeId : node.id , startX : e.clientX , startY : e.clientY , originX : node.x , originY : node.y } ;
} }
onPointerMove = { ( e ) = > {
const drag = nodeDragRef . current ;
if ( ! drag . active || drag . nodeId !== node . id ) return ;
const zoom = previewZoomRef . current ;
const dx = ( e . clientX - drag . startX ) / zoom ;
const dy = ( e . clientY - drag . startY ) / zoom ;
setCanvasNodes ( ( prev ) = > prev . map ( ( n ) = > n . id === node . id ? { . . . n , x : drag.originX + dx , y : drag.originY + dy } : n ) ) ;
} }
onPointerUp = { ( e ) = > {
if ( nodeDragRef . current . nodeId === node . id ) {
nodeDragRef . current = { . . . nodeDragRef . current , active : false } ;
e . currentTarget . releasePointerCapture ( e . pointerId ) ;
}
} }
/ >
< div className = "clone-ai-source-stack" >
< button
type = "button"
className = "clone-ai-source-corner-action"
onClick = { node . sourceImage ? ( ) = > openProductSetPreview ( { src : node.sourceImage ! , label : "原图素材" } ) : undefined }
disabled = { ! node . sourceImage }
>
原 图 素 材
< / button >
) ) }
< / div >
< / div >
< / article >
) ) }
{ status === "generating" ? (
< article className = "clone-ai-canvas-node is-generating" style = { { transform : ` translate( ${ canvasNodes . length * 420 } px, 0px) ` } } >
< LoadingOutlined style = { { fontSize : 24 } } / >
< span > 正 在 生 成 { selectedCloneOutput . label } … < / span >
< / article >
) : null }
< / section >
< button
type = "button"
className = "clone-ai-main-result"
aria - label = "预览原图素材"
onClick = { node . sourceImage ? ( ) = > openProductSetPreview ( { src : node.sourceImage ! , label : "原图素材" } ) : undefined }
disabled = { ! node . sourceImage }
>
{ node . sourceImage ? (
< img
src = { node . sourceImage }
alt = "原图素材"
onError = { ( event ) = > {
event . currentTarget . style . display = "none" ;
event . currentTarget . parentElement ? . classList . add ( "is-missing-source" ) ;
} }
/ >
) : null }
< span className = "clone-ai-source-missing" > 素 材 不 可 用 < / span >
< / button >
< / div >
< div className = "clone-ai-flow-arrow" aria-hidden = "true" / >
< div className = "clone-ai-result-stack" >
< span className = "clone-ai-node-label" > { node . mode === "set" ? "套图" : node . mode === "detail" ? "详情图" : node . mode === "model" ? "模特图" : node . mode === "hot" ? "爆款图" : node . mode } < / span >
< div className = "clone-ai-result-grid result-reveal" >
{ node . results . map ( ( card ) = > (
< button key = { card . id } type = "button" style = { { aspectRatio : parseRatioToAspectCss ( ratio ) } } onClick = { ( ) = > openProductSetPreview ( card , { nodeId : node.id , removable : true } ) } >
< img src = { card . src } alt = { card . label } / >
< / button >
) ) }
< / div >
< / div >
< / article >
) ) }
{ status === "generating" ? (
< article className = "clone-ai-canvas-node is-generating" style = { { transform : ` translate( ${ canvasNodes . length * 420 } px, 0px) ` } } >
< LoadingOutlined style = { { fontSize : 24 } } / >
< span > 正 在 生 成 { selectedCloneOutput . label } … < / span >
< / article >
) : null }
< / section >
) }
< / div >
) : status === "idle" || status === "ready" ? null : (
< section className = "clone-ai-empty-state" aria-live = "polite" >
@@ -6341,13 +6606,71 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
< section
ref = { commandComposerWrapRef }
className = { ` clone-ai-bottom-input ecom-command-composer-wrap ${ status === "done" || canvasNodes . length > 0 ? " has-generated" : " is-before-generate" } ${ isCommandComposerCompact && ( status === "done" || canvasNodes . length > 0 ) ? " is-compact " : "" } ` }
aria - label = "生成指令"
className = { ` clone-ai-bottom-input ecom-command-composer-wrap ${ hasGeneratedCloneWork ? " has-generated" : " is-before-generate" } ${ shouldUseCompactComposer ? " is-compact" : "" } ${ isRecordDetailWorkspace && isCloneConversationCollapsed ? " is-recall-entry " : "" } ` }
aria - label = { isRecordDetailWorkspace && isCloneConversationCollapsed ? "展开完整生成指令" : "生成指令" }
role = { isRecordDetailWorkspace && isCloneConversationCollapsed ? "button" : undefined }
tabIndex = { isRecordDetailWorkspace && isCloneConversationCollapsed ? 0 : undefined }
onClickCapture = { ( event ) = > {
if ( isRecordDetailWorkspace && isCloneConversationCollapsed ) {
if ( ( event . target as HTMLElement ) . closest ( ".ecom-command-recall__home" ) ) return ;
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
setIsCloneConversationCollapsed ( false ) ;
window . setTimeout ( ( ) = > requirementTextareaRef . current ? . focus ( ) , 180 ) ;
}
} }
onClick = { ( ) = > {
if ( isRecordDetailWorkspace && isCloneConversationCollapsed ) {
setIsCloneConversationCollapsed ( false ) ;
window . setTimeout ( ( ) = > requirementTextareaRef . current ? . focus ( ) , 180 ) ;
return ;
}
if ( isCommandComposerCompact ) setIsCommandComposerCompact ( false ) ;
} }
onKeyDown = { ( event ) = > {
if ( ! isRecordDetailWorkspace || ! isCloneConversationCollapsed ) return ;
if ( event . key !== "Enter" && event . key !== " " ) return ;
event . preventDefault ( ) ;
setIsCloneConversationCollapsed ( false ) ;
window . setTimeout ( ( ) = > requirementTextareaRef . current ? . focus ( ) , 180 ) ;
} }
>
< h1 className = { ` ecom-command-title ${ status === "done" || canvasNodes . length > 0 ? " is-after-generate" : "" } ` } >
{ isRecordDetailWorkspace && isCloneConversationCollapsed ? (
< div className = "ecom-command-recall" >
< span className = "ecom-command-recall__media" aria-hidden = "true" >
{ commandRecallAsset ? (
< img src = { commandRecallAsset . src } alt = "" / >
) : (
< PaperClipOutlined / >
) }
< / span >
< span className = "ecom-command-recall__content" >
< span className = "ecom-command-recall__eyebrow" >
< em > { commandRecallStatus } < / em >
< / span >
< span className = "ecom-command-recall__text" > { commandRecallPrompt } < / span >
< / span >
< button
type = "button"
className = "ecom-command-recall__home"
onClick = { ( event ) = > {
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
handleNewEcommerceConversation ( ) ;
} }
aria - label = "返回首页"
title = "返回首页"
>
< AppstoreOutlined / >
< span > 首 页 < / span >
< / button >
< span className = "ecom-command-recall__action" aria-hidden = "true" >
< span > 继 续 < / span >
< MenuUnfoldOutlined / >
< / span >
< / div >
) : null }
< h1 className = { ` ecom-command-title ${ hasGeneratedCloneWork ? " is-after-generate" : "" } ` } >
{ typewriterText }
< span className = "typewriter-cursor" aria-hidden = "true" > | < / span >
< / h1 >
@@ -7127,6 +7450,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
< / aside >
< section className = "ecom-image-workbench-stage" >
< header className = "ecom-visual-workspace-head ecom-copywriting-preview-head" >
< h1 > 图 片 修 改 < / h1 >
< p > 上 传 图 片 后 涂 抹 需 要 调 整 的 区 域 , < span > AI < / span > 将 根 据 提 示 完 成 局 部 重 绘 。 < / p >
< / header >
{ ! imageWorkbenchImage ? (
< div
className = { ` ecom-watermark-dropzone ${ isImageWorkbenchDragging ? " is-dragging" : "" } ` }
@@ -7385,6 +7712,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
< / aside >
< section className = "ecom-watermark-workspace" >
< header className = "ecom-visual-workspace-head ecom-copywriting-preview-head" >
< h1 > 图 片 翻 译 < / h1 >
< p > 上 传 含 文 字 的 图 片 并 选 择 目 标 语 种 , < span > AI < / span > 将 识 别 文 字 并 保 持 原 图 排 版 。 < / p >
< / header >
{ ! translateImage ? (
< div
className = { ` ecom-watermark-dropzone ${ isTranslateDragging ? " is-dragging" : "" } ` }
@@ -7514,7 +7845,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
< div
role = "button"
tabIndex = { 0 }
className = { ` ecom-quick-set-upload ${ detailProductImages . length ? " has-images" : "" } ` }
className = { ` ecom-quick-set-upload ecom-quick-hot-material ${ detailProductImages . length ? " has-images" : "" } ` }
onClick = { ( ) = > detailInputRef . current ? . click ( ) }
onKeyDown = { ( event ) = > openQuickUploadWithKeyboard ( event , detailInputRef ) }
onDragOver = { ( event ) = > event . preventDefault ( ) }
@@ -7524,7 +7855,24 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
< span > 拖 拽 或 点 击 上 传 < / span >
< em > 同 一 产 品 , 最 多 3 张 < / em >
< b > + 上 传 图 片 < / b >
{ detailProductImages . length ? renderQuickUploadThumbs ( detailProductImages , removeDetailImage ) : null }
{ detailProductImages . length ? (
< >
{ renderHotMaterialThumbs ( detailProductImages , removeDetailImage ) }
{ detailProductImages . length < 3 ? (
< button
type = "button"
className = "ecom-quick-hot-add-btn"
aria - label = "Add more detail images"
onClick = { ( event ) = > {
event . stopPropagation ( ) ;
detailInputRef . current ? . click ( ) ;
} }
>
< PlusOutlined / >
< / button >
) : null }
< / >
) : null }
< / div >
< input
ref = { detailInputRef }
@@ -7610,6 +7958,17 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
< button type = "button" className = "ecom-quick-set-primary ecom-quick-set-primary--cancel" onClick = { handleCancelGenerate } > 取 消 生 成 < / button >
) : null }
< / aside >
{ hotMaterialHoverZoom && typeof document !== "undefined"
? createPortal (
< div
className = { ` ecom-hot-material-zoom-portal is- ${ hotMaterialHoverZoom . placement } ` }
style = { { left : hotMaterialHoverZoom.x , top : hotMaterialHoverZoom.y } }
>
< img src = { hotMaterialHoverZoom . src } alt = "" / >
< / div > ,
document . body ,
)
: null }
< section className = "ecom-quick-set-stage" >
< header className = "ecom-quick-set-preview-head" >
< h1 > 预 览 < / h1 >
@@ -7978,7 +8337,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
onDragLeave = { ( event ) = > { event . preventDefault ( ) ; event . stopPropagation ( ) ; if ( event . currentTarget === event . target || ! event . currentTarget . contains ( event . relatedTarget as Node ) ) setIsProductUploadDragging ( false ) ; } }
onDrop = { ( event ) = > { event . preventDefault ( ) ; event . stopPropagation ( ) ; setIsProductUploadDragging ( false ) ; const files = Array . from ( event . dataTransfer . files ) ; if ( files . length ) addProductImages ( files ) ; } }
>
{ renderQuickUploadThumbs ( productImages , removeProductImage ) }
{ renderHotMaterialThumbs ( productImages , removeProductImage ) }
< button
type = "button"
className = "ecom-quick-hot-add-btn"
@@ -8137,6 +8496,17 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
< / button >
< / div >
< / aside >
{ hotMaterialHoverZoom && typeof document !== "undefined"
? createPortal (
< div
className = { ` ecom-hot-material-zoom-portal is- ${ hotMaterialHoverZoom . placement } ` }
style = { { left : hotMaterialHoverZoom.x , top : hotMaterialHoverZoom.y } }
>
< img src = { hotMaterialHoverZoom . src } alt = "" / >
< / div > ,
document . body ,
)
: null }
< section className = "ecom-quick-set-stage" >
< header className = "ecom-quick-set-preview-head" >
< h1 > 预 览 < / h1 >
@@ -8311,11 +8681,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
< / main >
) ;
const copywritingPreview = (
< div key = "copywriting" className = "ecom-quick-page-wrap ecom-tool-page-enter" >
< EcommerceCopywritingPanel onClose = { closeCopywritingPage } / >
< / div >
) ;
const copywritingPreview = < EcommerceCopywritingPanel onClose = { closeCopywritingPage } / > ;
const oneClickVideoPreview = (
< div key = "oneClickVideo" className = "ecom-quick-page-wrap ecom-tool-page-enter" >
@@ -8361,59 +8727,66 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
? tryOnPreview
: isCloneTool
? isWatermarkTool
? watermarkPreview
? (
< div key = { ` quick- ${ activeQuickTool } ` } className = "ecom-quick-page-wrap ecom-tool-page-enter" >
{ renderQuickPageSidebar ( "watermark" , "visual" ) }
{ watermarkPreview }
< / div >
)
: isTranslateTool
? translatePreview
? (
< div key = { ` quick- ${ activeQuickTool } ` } className = "ecom-quick-page-wrap ecom-tool-page-enter" >
{ renderQuickPageSidebar ( "translate" , "visual" ) }
{ translatePreview }
< / div >
)
: isImageEditTool
? imageWorkbenchPreview
? (
< div key = { ` quick- ${ activeQuickTool } ` } className = "ecom-quick-page-wrap ecom-tool-page-enter" >
{ renderQuickPageSidebar ( "image-edit" , "visual" ) }
{ imageWorkbenchPreview }
< / div >
)
: isSmartCutoutTool
? smartCutoutPreview
: isQuickDetailTool
? (
< div key = { ` quick- ${ activeQuickTool } ` } className = "ecom-quick-page-wrap ecom-tool-page-enter" >
{ renderQuickPageSidebar ( "detail" ) }
{ quickDetailPreview }
< / div >
)
: isHotCloneTool
? (
< div key = { ` quick- ${ activeQuickTool } ` } className = "ecom-quick-page-wrap ecom-tool-page-enter" >
{ renderQuickPageSidebar ( "hot" ) }
{ hotClonePreview }
< / div >
)
: isQuickSetTool
? (
< div key = { ` quick- ${ activeQuickTool } ` } className = "ecom-quick-page-wrap ecom-tool-page-enter" >
{ renderQuickPageSidebar ( "quick-set" ) }
{ quickSetGenPreview }
< / div >
)
: isCopywritingTool
? copywritingPreview
? (
< div key = { ` quick- ${ activeQuickTool } ` } className = "ecom-quick-page-wrap ecom-tool-page-enter" >
{ renderQuickPageSidebar ( "copywriting" , "visual" ) }
{ copywritingPreview }
< / div >
)
: isOneClickVideoTool
? oneClickVideoPreview
? (
< div key = { ` quick- ${ activeQuickTool } ` } className = "ecom-quick-page-wrap ecom-one-click-video-wrap ecom-tool-page-enter" >
{ renderQuickPageSidebar ( "oneClickVideo" ) }
{ oneClickVideoPreview }
< / div >
)
: clonePreview
: placeholderPreview ;
const isMainCloneWorkspace = isCloneTool && ! isSmartCutoutTool && ! isQuickDetailTool && ! isWatermarkTool && ! isTranslateTool && ! isImageEditTool && ! isQuickSetTool && ! isCopywritingTool && ! isOneClickVideoTool ;
const isRecordDetailWorkspace = isMainCloneWorkspace && Boolean ( activeHistoryRecordId ) ;
const currentResultCount = canvasNodes . reduce ( ( count , node ) = > count + node . results . length , 0 ) ;
const activeHistoryRecord = activeHistoryRecordId ? ecommerceHistoryRecords . find ( ( record ) = > record . id === activeHistoryRecordId ) : null ;
const activeConversationTurns = activeHistoryRecord
? activeHistoryRecord . turns ? . length
? activeHistoryRecord . turns
: [ buildHistoryTurnFromRecord ( activeHistoryRecord ) ]
: [ ] ;
const getHistoryTurnSettingLabel = ( turn : EcommerceHistoryTurn ) = > {
if ( turn . settingLabel ) return turn . settingLabel ;
if ( turn . output === "set" && turn . results ? . length && ! turn . setResultImages ? . length ) {
return ` 单图 ${ turn . results . length } 张 ` ;
}
if ( turn . output === "set" ) {
const total = cloneSetCountKeys . reduce ( ( sum , key ) = > sum + ( turn . setCounts ? . [ key ] ? ? 0 ) , 0 ) ;
return ` 套图 ${ total || 1 } 张 ` ;
}
if ( turn . output === "detail" ) return ` 详情 ${ turn . detailModules ? . length || 1 } 项 ` ;
if ( turn . output === "model" ) return ` 模特 ${ turn . modelScenes ? . length || 1 } 景 ` ;
return cloneOutputOptions . find ( ( option ) = > option . key === turn . output ) ? . label || selectedCloneOutput . label ;
} ;
const restoreHistoryTurnInputs = ( turn : EcommerceHistoryTurn ) = > {
setCloneOutput ( turn . output ) ;
setPlatform ( turn . platform ) ;
@@ -8474,18 +8847,30 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
< >
< aside className = "clone-ai-conversation-panel" aria-label = "AI 对话" >
< header className = "clone-ai-conversation-head" >
< div >
< div className = "clone-ai-conversation-title" >
< strong > { activeHistoryRecord ? . title || "生成详情" } < / strong >
< span > { selectedCloneOutput . label } · { platform } · { language } < / span >
< / div >
< button
type = "button"
onClick = { ( ) = > setIsCloneConversationCollapsed ( true ) }
aria - label = "收起对话 "
title = "收起对话"
>
< MenuFoldOutlined / >
< / button >
< div className = "clone-ai-conversation-actions" >
< button
type = "button"
className = "clone-ai-conversation-home "
onClick = { handleNewEcommerceConversation }
aria - label = "返回首页"
title = "返回首页"
>
< AppstoreOutlined / >
< span > 首 页 < / span >
< / button >
< button
type = "button"
className = "clone-ai-conversation-collapse"
onClick = { ( ) = > setIsCloneConversationCollapsed ( true ) }
aria - label = "收起对话"
title = "收起对话"
>
< MenuFoldOutlined / >
< / button >
< / div >
< / header >
< div className = "clone-ai-conversation-body" >
{ activeConversationTurns . map ( ( turn , index ) = > {
@@ -8557,16 +8942,6 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
} ) }
< / div >
< / aside >
< button
type = "button"
className = "clone-ai-conversation-toggle"
onClick = { ( ) = > setIsCloneConversationCollapsed ( ( current ) = > ! current ) }
aria - label = { isCloneConversationCollapsed ? "展开对话" : "收起对话" }
title = { isCloneConversationCollapsed ? "展开对话" : "收起对话" }
aria - expanded = { ! isCloneConversationCollapsed }
>
{ isCloneConversationCollapsed ? < MenuUnfoldOutlined / > : < MenuFoldOutlined / > }
< / button >
< / >
) : null }