From 7636333978f5f80b5acad2ff18d81704e9f1eb41 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 10 Jun 2026 17:52:01 +0800 Subject: [PATCH] feat: refine ecommerce smart cutout editor --- src/features/ecommerce/EcommercePage.tsx | 724 +++++++++++- src/styles/ecommerce-standalone.css | 1320 ++++++++++++++++++++++ 2 files changed, 2029 insertions(+), 15 deletions(-) diff --git a/src/features/ecommerce/EcommercePage.tsx b/src/features/ecommerce/EcommercePage.tsx index cd97477..2d72959 100644 --- a/src/features/ecommerce/EcommercePage.tsx +++ b/src/features/ecommerce/EcommercePage.tsx @@ -3,7 +3,9 @@ CloudUploadOutlined, CloseOutlined, FileImageOutlined, + FolderOpenOutlined, FrownOutlined, + GlobalOutlined, LoadingOutlined, MenuFoldOutlined, MenuUnfoldOutlined, @@ -11,8 +13,9 @@ ReloadOutlined, SettingOutlined, SkinOutlined, + TableOutlined, } from "@ant-design/icons"; -import { useEffect, useMemo, useRef, useState, type CSSProperties, type ChangeEvent, type DragEvent, type MouseEvent as ReactMouseEvent, type ReactNode } from "react"; +import { useEffect, useMemo, useRef, useState, type CSSProperties, type ChangeEvent, type DragEvent, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type ReactNode } from "react"; import "../../styles/pages/ecommerce.css"; import "../../styles/pages/local-theme-parity.css"; import { ossAssets } from "../../data/ossAssets"; @@ -35,6 +38,136 @@ import pinduoduoLogo from "../../assets/platform-logos/pinduoduo.webp"; import shopeeLogo from "../../assets/platform-logos/shopee.webp"; import taobaoLogo from "../../assets/platform-logos/taobao.webp"; import tiktokShopLogo from "../../assets/platform-logos/tiktok-shop.webp"; + +const smartCutoutColorPresets = [ + "#ffffff", + "#111111", + "#ff3131", + "#ff7a1a", + "#f7c600", + "#29b34a", + "#25a9e0", + "#438df5", + "#9029d9", + "#8aa3ad", + "#6b7b86", + "#f46f7b", + "#ff9451", + "#f7d34f", + "#55c66f", + "#73c7f3", + "#6dabf5", + "#b45adb", + "#bcc8ce", + "#aeb7bd", + "#ffbec4", + "#ffd1ac", + "#f8e69d", + "#91de9e", + "#b7e5fb", + "#b9d9fb", + "#d7abe8", + "#dfe5e8", + "#d7dde0", + "#ffe2e4", + "#ffe5d1", + "#f8efcf", + "#c9efcf", + "#d8f0fb", + "#d8eafa", + "#ead2f1", +]; + +const smartCutoutSizeOptions = [ + { key: "original", label: "原尺寸", icon: "image", frameWidth: "min(520px, 78%)", frameAspect: "auto", imageMaxWidth: "78%", imageMaxHeight: "310px" }, + { key: "trim", label: "裁剪到边缘", icon: "crop", frameWidth: "min(420px, 70%)", frameAspect: "auto", imageMaxWidth: "92%", imageMaxHeight: "360px" }, + { key: "taobao-1-1", label: "淘宝1:1主图", icon: "shop", frameWidth: "min(430px, 72%)", frameAspect: "1 / 1", imageMaxWidth: "82%", imageMaxHeight: "82%" }, + { key: "taobao-3-4", label: "淘宝3:4主图", icon: "shop", frameWidth: "min(330px, 56%)", frameAspect: "3 / 4", imageMaxWidth: "82%", imageMaxHeight: "82%" }, + { key: "pdd-main", label: "拼多多主图", icon: "pdd", frameWidth: "min(430px, 72%)", frameAspect: "1 / 1", imageMaxWidth: "82%", imageMaxHeight: "82%" }, + { key: "xiaohongshu-cover", label: "小红书封面", icon: "text", frameWidth: "min(330px, 56%)", frameAspect: "3 / 4", imageMaxWidth: "82%", imageMaxHeight: "82%" }, + { key: "one-inch", label: "一寸头像", icon: "portrait", frameWidth: "min(290px, 50%)", frameAspect: "25 / 35", imageMaxWidth: "86%", imageMaxHeight: "86%" }, + { key: "two-inch", label: "二寸头像", icon: "portrait", frameWidth: "min(320px, 54%)", frameAspect: "35 / 49", imageMaxWidth: "86%", imageMaxHeight: "86%" }, + { key: "ratio-1-1", label: "1:1", icon: "square", frameWidth: "min(430px, 72%)", frameAspect: "1 / 1", imageMaxWidth: "82%", imageMaxHeight: "82%" }, + { key: "ratio-3-2", label: "3:2", icon: "landscape", frameWidth: "min(520px, 78%)", frameAspect: "3 / 2", imageMaxWidth: "82%", imageMaxHeight: "82%" }, + { key: "ratio-2-3", label: "2:3", icon: "portrait-ratio", frameWidth: "min(330px, 56%)", frameAspect: "2 / 3", imageMaxWidth: "82%", imageMaxHeight: "82%" }, + { key: "ratio-4-3", label: "4:3", icon: "landscape", frameWidth: "min(520px, 78%)", frameAspect: "4 / 3", imageMaxWidth: "82%", imageMaxHeight: "82%" }, + { key: "ratio-3-4", label: "3:4", icon: "portrait-ratio", frameWidth: "min(330px, 56%)", frameAspect: "3 / 4", imageMaxWidth: "82%", imageMaxHeight: "82%" }, + { key: "ratio-16-9", label: "16:9", icon: "wide", frameWidth: "min(560px, 82%)", frameAspect: "16 / 9", imageMaxWidth: "82%", imageMaxHeight: "82%" }, + { key: "ratio-9-16", label: "9:16", icon: "phone", frameWidth: "min(260px, 46%)", frameAspect: "9 / 16", imageMaxWidth: "82%", imageMaxHeight: "82%" }, +] as const; + +type SmartCutoutSizeKey = (typeof smartCutoutSizeOptions)[number]["key"]; + +const clampNumber = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value)); + +const normalizeHexColor = (value: string) => { + const clean = value.trim().replace(/^#/, ""); + if (!/^[0-9a-fA-F]{6}$/.test(clean)) return null; + return `#${clean.toLowerCase()}`; +}; + +const hexToRgb = (value: string) => { + const normalized = normalizeHexColor(value); + if (!normalized) return null; + const numeric = Number.parseInt(normalized.slice(1), 16); + return { + r: (numeric >> 16) & 255, + g: (numeric >> 8) & 255, + b: numeric & 255, + }; +}; + +const rgbToHex = (r: number, g: number, b: number) => + `#${[r, g, b].map((item) => clampNumber(Math.round(item), 0, 255).toString(16).padStart(2, "0")).join("")}`; + +const hsvToRgb = (h: number, s: number, v: number) => { + const hue = ((h % 360) + 360) % 360; + const saturation = clampNumber(s, 0, 100) / 100; + const value = clampNumber(v, 0, 100) / 100; + const chroma = value * saturation; + const x = chroma * (1 - Math.abs(((hue / 60) % 2) - 1)); + const match = value - chroma; + const [red, green, blue] = + hue < 60 + ? [chroma, x, 0] + : hue < 120 + ? [x, chroma, 0] + : hue < 180 + ? [0, chroma, x] + : hue < 240 + ? [0, x, chroma] + : hue < 300 + ? [x, 0, chroma] + : [chroma, 0, x]; + return { + r: (red + match) * 255, + g: (green + match) * 255, + b: (blue + match) * 255, + }; +}; + +const hexToHsv = (value: string) => { + const rgb = hexToRgb(value) ?? { r: 255, g: 255, b: 255 }; + const red = rgb.r / 255; + const green = rgb.g / 255; + const blue = rgb.b / 255; + const max = Math.max(red, green, blue); + const min = Math.min(red, green, blue); + const delta = max - min; + const hue = + delta === 0 + ? 0 + : max === red + ? 60 * (((green - blue) / delta) % 6) + : max === green + ? 60 * ((blue - red) / delta + 2) + : 60 * ((red - green) / delta + 4); + return { + h: Math.round((hue + 360) % 360), + s: max === 0 ? 0 : Math.round((delta / max) * 100), + v: Math.round(max * 100), + }; +}; import tmallLogo from "../../assets/platform-logos/tmall.webp"; import { aiGenerationClient } from "../../api/aiGenerationClient"; import { ServerRequestError } from "../../api/serverConnection"; @@ -991,6 +1124,12 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const setInputRef = useRef(null); const productInputRef = useRef(null); const cloneReferenceInputRef = useRef(null); + const smartCutoutInputRef = useRef(null); + const smartCutoutTransitionTimeoutRef = useRef(null); + const smartCutoutPendingUrlsRef = useRef([]); + const smartCutoutPaletteRef = useRef(null); + const smartCutoutToolsRef = useRef(null); + const composerMenuCloseTimeoutRef = useRef(null); const requirementTextareaRef = useRef(null); const commandComposerWrapRef = useRef(null); const garmentInputRef = useRef(null); @@ -1025,12 +1164,28 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const [selectedProductSetPreview, setSelectedProductSetPreview] = useState<{ src: string; label: string } | null>(null); const [showHostingModal, setShowHostingModal] = useState(false); const [productImages, setProductImages] = useState([]); + const [activeQuickTool, setActiveQuickTool] = useState<"cutout" | null>(null); + const [smartCutoutImage, setSmartCutoutImage] = useState<{ src: string; name: string } | null>(null); + const [smartCutoutBatchImages, setSmartCutoutBatchImages] = useState<{ src: string; name: string }[]>([]); + const [smartCutoutBackgroundColor, setSmartCutoutBackgroundColor] = useState("#ffffff"); + const [smartCutoutBackgroundAlpha, setSmartCutoutBackgroundAlpha] = useState(100); + const [smartCutoutHexDraft, setSmartCutoutHexDraft] = useState("#ffffff"); + const [isSmartCutoutPaletteOpen, setIsSmartCutoutPaletteOpen] = useState(false); + const [smartCutoutSizeKey, setSmartCutoutSizeKey] = useState("original"); + const [isSmartCutoutDragging, setIsSmartCutoutDragging] = useState(false); + const [isSmartCutoutTransitioning, setIsSmartCutoutTransitioning] = useState(false); + const [smartCutoutTransitionMessage, setSmartCutoutTransitionMessage] = useState({ + title: "正在切换页面", + subtitle: "请稍候", + }); const [isProductUploadDragging, setIsProductUploadDragging] = useState(false); const [cloneOutput, setCloneOutput] = useState(defaultCloneOutput); const [videoHistoryVisible, setVideoHistoryVisible] = useState(false); const [videoPlanTrigger, setVideoPlanTrigger] = useState(0); const [openCloneBasicSelect, setOpenCloneBasicSelect] = useState(null); const [composerMenu, setComposerMenu] = useState(null); + const [visibleComposerMenu, setVisibleComposerMenu] = useState(null); + const [isComposerMenuClosing, setIsComposerMenuClosing] = useState(false); const [composerPopoverLeft, setComposerPopoverLeft] = useState(0); const [isCommandHistoryCollapsed, setIsCommandHistoryCollapsed] = useState(false); const [openCloneModelSelect, setOpenCloneModelSelect] = useState(null); @@ -1414,6 +1569,219 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { if (files.length) void addSetImages(files); }; + const revokeSmartCutoutItems = (items: { src: string }[]) => { + items.forEach((item) => URL.revokeObjectURL(item.src)); + }; + + const clearSmartCutoutTransition = () => { + if (smartCutoutTransitionTimeoutRef.current !== null) { + window.clearTimeout(smartCutoutTransitionTimeoutRef.current); + smartCutoutTransitionTimeoutRef.current = null; + } + if (smartCutoutPendingUrlsRef.current.length) { + smartCutoutPendingUrlsRef.current.forEach((url) => URL.revokeObjectURL(url)); + smartCutoutPendingUrlsRef.current = []; + } + setIsSmartCutoutTransitioning(false); + }; + + const runSmartCutoutPageTransition = (message: { title: string; subtitle: string }, action: () => void, delay = 460) => { + clearSmartCutoutTransition(); + setSmartCutoutTransitionMessage(message); + setIsSmartCutoutTransitioning(true); + smartCutoutTransitionTimeoutRef.current = window.setTimeout(() => { + smartCutoutTransitionTimeoutRef.current = null; + action(); + setIsSmartCutoutTransitioning(false); + }, delay); + }; + + const openSmartCutoutUpload = () => { + clearSmartCutoutTransition(); + setSmartCutoutTransitionMessage({ + title: "正在进入智能抠图", + subtitle: "为你打开图片处理工具", + }); + setActiveQuickTool("cutout"); + setSmartCutoutBatchImages((current) => { + revokeSmartCutoutItems(current); + return []; + }); + setSmartCutoutImage((current) => { + if (current?.src) URL.revokeObjectURL(current.src); + return null; + }); + setComposerMenu(null); + }; + + const closeSmartCutoutTool = () => { + runSmartCutoutPageTransition( + { + title: "正在返回首页", + subtitle: "回到电商智能体", + }, + () => { + setSmartCutoutBatchImages((current) => { + revokeSmartCutoutItems(current); + return []; + }); + setSmartCutoutImage((current) => { + if (current?.src) URL.revokeObjectURL(current.src); + return null; + }); + setActiveQuickTool(null); + setComposerMenu(null); + }, + ); + }; + + const goSmartCutoutPrevious = () => { + if (!smartCutoutImage) { + closeSmartCutoutTool(); + return; + } + runSmartCutoutPageTransition( + { + title: "正在返回上一页", + subtitle: "回到图片上传页", + }, + () => { + setSmartCutoutBatchImages((current) => { + revokeSmartCutoutItems(current); + return []; + }); + setSmartCutoutImage((current) => { + if (current?.src) URL.revokeObjectURL(current.src); + return null; + }); + }, + ); + }; + + const addSmartCutoutImage = (files: File[]) => { + const imageFiles = files.filter((file) => file.type.startsWith("image/")); + if (!imageFiles.length) { + toast.error("请上传图片文件"); + return; + } + clearSmartCutoutTransition(); + setSmartCutoutBatchImages((current) => { + revokeSmartCutoutItems(current); + return []; + }); + setSmartCutoutImage((current) => { + if (current?.src) URL.revokeObjectURL(current.src); + return null; + }); + const nextImages = imageFiles.map((file) => ({ src: URL.createObjectURL(file), name: file.name })); + smartCutoutPendingUrlsRef.current = nextImages.map((item) => item.src); + setActiveQuickTool("cutout"); + setSmartCutoutSizeKey("original"); + setSmartCutoutTransitionMessage({ + title: imageFiles.length > 1 ? "正在批量抠图" : "正在智能抠图", + subtitle: imageFiles.length > 1 ? `正在处理 ${imageFiles.length} 张图片` : "即将进入图片编辑室", + }); + setIsSmartCutoutTransitioning(true); + smartCutoutTransitionTimeoutRef.current = window.setTimeout(() => { + smartCutoutTransitionTimeoutRef.current = null; + smartCutoutPendingUrlsRef.current = []; + setSmartCutoutBatchImages(nextImages); + setSmartCutoutImage(nextImages[0]); + setIsSmartCutoutTransitioning(false); + }, 620); + }; + + const handleSmartCutoutUpload = (event: ChangeEvent) => { + const files = event.target.files; + if (!files?.length) return; + addSmartCutoutImage(Array.from(files)); + event.target.value = ""; + }; + + const handleSmartCutoutDrop = (event: DragEvent) => { + event.preventDefault(); + setIsSmartCutoutDragging(false); + const files = Array.from(event.dataTransfer.files); + if (files.length) addSmartCutoutImage(files); + }; + + const smartCutoutBackgroundValue = useMemo(() => { + const rgb = hexToRgb(smartCutoutBackgroundColor) ?? { r: 255, g: 255, b: 255 }; + if (smartCutoutBackgroundAlpha >= 100) return smartCutoutBackgroundColor; + return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${Math.round(smartCutoutBackgroundAlpha) / 100})`; + }, [smartCutoutBackgroundAlpha, smartCutoutBackgroundColor]); + + const smartCutoutColorHsv = useMemo(() => hexToHsv(smartCutoutBackgroundColor), [smartCutoutBackgroundColor]); + + const selectedSmartCutoutSize = useMemo( + () => smartCutoutSizeOptions.find((option) => option.key === smartCutoutSizeKey) ?? smartCutoutSizeOptions[0], + [smartCutoutSizeKey], + ); + + const smartCutoutFrameStyle = useMemo( + () => ({ + "--smart-cutout-bg": smartCutoutBackgroundValue, + "--smart-cutout-frame-width": selectedSmartCutoutSize.frameWidth, + "--smart-cutout-frame-aspect": selectedSmartCutoutSize.frameAspect, + "--smart-cutout-image-max-width": selectedSmartCutoutSize.imageMaxWidth, + "--smart-cutout-image-max-height": selectedSmartCutoutSize.imageMaxHeight, + } as CSSProperties), + [selectedSmartCutoutSize, smartCutoutBackgroundValue], + ); + + const applySmartCutoutHsv = (h: number, s: number, v: number) => { + const rgb = hsvToRgb(h, s, v); + setSmartCutoutBackgroundColor(rgbToHex(rgb.r, rgb.g, rgb.b)); + }; + + const updateSmartCutoutColorFromPoint = (element: HTMLElement, clientX: number, clientY: number) => { + const rect = element.getBoundingClientRect(); + const saturation = clampNumber(((clientX - rect.left) / rect.width) * 100, 0, 100); + const value = clampNumber(100 - ((clientY - rect.top) / rect.height) * 100, 0, 100); + applySmartCutoutHsv(smartCutoutColorHsv.h, saturation, value); + }; + + const handleSmartCutoutColorPlanePointer = (event: ReactPointerEvent) => { + event.preventDefault(); + event.currentTarget.setPointerCapture(event.pointerId); + updateSmartCutoutColorFromPoint(event.currentTarget, event.clientX, event.clientY); + }; + + const handleSmartCutoutColorPlaneMove = (event: ReactPointerEvent) => { + if (event.buttons !== 1) return; + updateSmartCutoutColorFromPoint(event.currentTarget, event.clientX, event.clientY); + }; + + const handleSmartCutoutHexChange = (value: string) => { + const nextValue = value.startsWith("#") ? value : `#${value}`; + if (!/^#[0-9a-fA-F]{0,6}$/.test(nextValue)) return; + setSmartCutoutHexDraft(nextValue); + const normalized = normalizeHexColor(nextValue); + if (normalized) setSmartCutoutBackgroundColor(normalized); + }; + + const scrollSmartCutoutTools = (direction: -1 | 1) => { + smartCutoutToolsRef.current?.scrollBy({ + left: direction * 340, + behavior: "smooth", + }); + }; + + useEffect(() => { + setSmartCutoutHexDraft(smartCutoutBackgroundColor); + }, [smartCutoutBackgroundColor]); + + useEffect(() => { + if (!isSmartCutoutPaletteOpen) return undefined; + const handlePointerDown = (event: PointerEvent) => { + if (!smartCutoutPaletteRef.current?.contains(event.target as Node)) { + setIsSmartCutoutPaletteOpen(false); + } + }; + document.addEventListener("pointerdown", handlePointerDown); + return () => document.removeEventListener("pointerdown", handlePointerDown); + }, [isSmartCutoutPaletteOpen]); + const removeSetImage = (imageId: string) => { setSetImages((current) => { const next = current.filter((item) => item.id !== imageId); @@ -1869,6 +2237,34 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { return () => document.removeEventListener("pointerdown", handlePointerDown); }, [composerMenu, isCommandComposerCompact, status]); + useEffect(() => { + if (composerMenuCloseTimeoutRef.current !== null) { + window.clearTimeout(composerMenuCloseTimeoutRef.current); + composerMenuCloseTimeoutRef.current = null; + } + if (composerMenu) { + setVisibleComposerMenu(composerMenu); + setIsComposerMenuClosing(false); + return; + } + if (!visibleComposerMenu) return; + setIsComposerMenuClosing(true); + composerMenuCloseTimeoutRef.current = window.setTimeout(() => { + composerMenuCloseTimeoutRef.current = null; + setVisibleComposerMenu(null); + setIsComposerMenuClosing(false); + }, 220); + }, [composerMenu, visibleComposerMenu]); + + useEffect( + () => () => { + if (composerMenuCloseTimeoutRef.current !== null) { + window.clearTimeout(composerMenuCloseTimeoutRef.current); + } + }, + [], + ); + useEffect(() => { if (!openCloneModelSelect) return undefined; @@ -2462,6 +2858,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const isDetail = activeTool === "detail"; const isTryOn = activeTool === "wear"; const isCloneTool = activeTool === "clone"; + const isSmartCutoutTool = isCloneTool && activeQuickTool === "cutout"; const pageLabel = isSetTool ? "鍟嗗搧濂楀浘" : isDetail ? "A+/璇︽儏椤?" : isTryOn ? "AI鏈嶉グ绌挎埓" : activeToolMeta?.label || "鍟嗗搧宸ュ叿"; const setPrimaryLabel = setImages.length === 0 @@ -2948,10 +3345,13 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { countries: marketLanguageOptions.filter((option) => option.languages.includes(item)).map((option) => option.country), })); const composerPopoverStyle: CSSProperties = { left: composerPopoverLeft }; - if (!composerMenu) return null; - if (composerMenu === "mode") { + const menuToRender = composerMenu ?? visibleComposerMenu; + if (!menuToRender) return null; + const popoverClosingClass = !composerMenu && isComposerMenuClosing ? " is-closing" : ""; + const composerPopoverKey = `${menuToRender}-${cloneOutput}-${popoverClosingClass ? "closing" : "open"}`; + if (menuToRender === "mode") { return ( -
+
{visibleCloneOutputOptions.map((option) => (
); } - if (composerMenu === "platform") { + if (menuToRender === "platform") { return ( -
+
{platformOptions.map((option) => (
); } - if (composerMenu === "language") { + if (menuToRender === "language") { return ( -
+
{composerLanguageOptions.map((option) => (
); } - if (composerMenu === "ratio") { + if (menuToRender === "ratio") { return ( -
+
{cloneRatioOptions.map((option) => (
@@ -3455,11 +3868,292 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
{renderComposerMenu()}
+ {status !== "done" ? ( +
+ {[ + { label: "智能抠图", icon: , onClick: openSmartCutoutUpload }, + { label: "商品套图", icon: }, + { label: "图片修改", icon: }, + { label: "增/去水印", icon: }, + { label: "图片批处理", icon: }, + { label: "一键翻译", icon: }, + { label: "A+/详情页", icon: }, + { label: "变清晰", icon: }, + { label: "AI消除", icon: }, + { label: "证件照", icon: }, + { label: "爆款视频", icon: }, + { label: "拼图", icon: }, + ].map((item) => ( + + ))} +
+ ) : null} {requirement.length}/500 ); + const smartCutoutPreview = ( +
+ + + {isSmartCutoutTransitioning ? ( +
+
+ ) : null} + {!smartCutoutImage ? ( +
+
+ 智能抠图 + 3s 一键抠图,快速去除背景 +
+
+
+ ) : ( +
+
+
+
+ +
+ + +
+
+
+ +
+ {smartCutoutSizeOptions.map((item) => ( + + ))} +
+ +
+ {smartCutoutBatchImages.length > 1 ? ( +
+
+ 批量图片 + {smartCutoutBatchImages.length} 张 +
+
+ {smartCutoutBatchImages.map((image, index) => ( + + ))} +
+
+ ) : null} +
+
纯色
+
+ {["#ffffff", "#eeeae3", "#f2e3cf", "#000000", "#a89682", "#c9c9c9"].map((color) => ( + + ))} +
+
AI换背景
+
+ + {["客厅陈列", "桌面日光", "香氛产品", "绿植窗边", "居家空间"].map((item) => ( + + ))} +
+
+
+ +
+ )} +
+ ); + const detailPreview = (
@@ -3557,7 +4251,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { return (
@@ -3581,7 +4275,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { {isSetTool ? setPanel : isDetail ? detailPanel : isTryOn ? tryOnPanel : isCloneTool ? clonePanel : placeholderPanel} - {isCloneTool ? ( + {isCloneTool && !isSmartCutoutTool ? (