feat: add hot video page and enhance smart cutout editor

- Add 爆款视频 (hot video) page with platform, ratio, quality, duration settings
- Add AI video planning workspace integration
- Update smart cutout size options with precise output dimensions (295*413, 800*800, etc.)
- Add compare (对比) feature for before/after cutout preview
- Enhance smart cutout tool UI with size labels and improved layout
- Add hot video page styles and smart cutout editor refinements
This commit is contained in:
Codex
2026-06-11 13:56:29 +08:00
parent 5ba9464646
commit 4caa915922
2 changed files with 1197 additions and 63 deletions
+280 -48
View File
@@ -81,12 +81,12 @@ const smartCutoutColorPresets = [
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: "one-inch", label: "一寸头像", sizeLabel: "295*413", icon: "portrait", frameWidth: "min(290px, 50%)", frameAspect: "295 / 413", imageMaxWidth: "86%", imageMaxHeight: "86%", outputWidth: 295, outputHeight: 413 },
{ key: "two-inch", label: "二寸头像", sizeLabel: "413*579", icon: "portrait", frameWidth: "min(320px, 54%)", frameAspect: "413 / 579", imageMaxWidth: "86%", imageMaxHeight: "86%", outputWidth: 413, outputHeight: 579 },
{ key: "taobao-1-1", label: "淘宝1:1主图", sizeLabel: "800*800", icon: "shop", frameWidth: "min(430px, 72%)", frameAspect: "800 / 800", imageMaxWidth: "82%", imageMaxHeight: "82%", outputWidth: 800, outputHeight: 800 },
{ key: "taobao-3-4", label: "淘宝3:4主图", sizeLabel: "750*1000", icon: "shop", frameWidth: "min(330px, 56%)", frameAspect: "750 / 1000", imageMaxWidth: "82%", imageMaxHeight: "82%", outputWidth: 750, outputHeight: 1000 },
{ key: "pdd-main", label: "拼多多主图", sizeLabel: "800*800", icon: "pdd", frameWidth: "min(430px, 72%)", frameAspect: "800 / 800", imageMaxWidth: "82%", imageMaxHeight: "82%", outputWidth: 800, outputHeight: 800 },
{ key: "xiaohongshu-cover", label: "小红书封面", sizeLabel: "1242*1660", icon: "text", frameWidth: "min(330px, 56%)", frameAspect: "1242 / 1660", imageMaxWidth: "82%", imageMaxHeight: "82%", outputWidth: 1242, outputHeight: 1660 },
{ 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%" },
@@ -97,6 +97,7 @@ const smartCutoutSizeOptions = [
] as const;
type SmartCutoutSizeKey = (typeof smartCutoutSizeOptions)[number]["key"];
type SmartCutoutImageItem = { src: string; name: string; originalSrc?: string };
const clampNumber = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));
@@ -1214,15 +1215,16 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const [selectedProductSetPreview, setSelectedProductSetPreview] = useState<{ src: string; label: string } | null>(null);
const [showHostingModal, setShowHostingModal] = useState(false);
const [productImages, setProductImages] = useState<CloneImageItem[]>([]);
const [activeQuickTool, setActiveQuickTool] = useState<"cutout" | "set" | "detail" | "watermark" | "image-edit" | null>(null);
const [smartCutoutImage, setSmartCutoutImage] = useState<{ src: string; name: string } | null>(null);
const [smartCutoutBatchImages, setSmartCutoutBatchImages] = useState<{ src: string; name: string }[]>([]);
const [activeQuickTool, setActiveQuickTool] = useState<"cutout" | "set" | "detail" | "watermark" | "image-edit" | "hot-video" | null>(null);
const [smartCutoutImage, setSmartCutoutImage] = useState<SmartCutoutImageItem | null>(null);
const [smartCutoutBatchImages, setSmartCutoutBatchImages] = useState<SmartCutoutImageItem[]>([]);
const [smartCutoutBackgroundColor, setSmartCutoutBackgroundColor] = useState("#ffffff");
const [smartCutoutBackgroundAlpha, setSmartCutoutBackgroundAlpha] = useState(100);
const [smartCutoutHexDraft, setSmartCutoutHexDraft] = useState("#ffffff");
const [isSmartCutoutPaletteOpen, setIsSmartCutoutPaletteOpen] = useState(false);
const [smartCutoutSizeKey, setSmartCutoutSizeKey] = useState<SmartCutoutSizeKey>("original");
const [isSmartCutoutDragging, setIsSmartCutoutDragging] = useState(false);
const [isSmartCutoutComparing, setIsSmartCutoutComparing] = useState(false);
const [isSmartCutoutTransitioning, setIsSmartCutoutTransitioning] = useState(false);
const [smartCutoutTransitionMessage, setSmartCutoutTransitionMessage] = useState({
title: "正在切换页面",
@@ -1656,8 +1658,14 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
if (files.length) void addSetImages(files);
};
const revokeSmartCutoutItems = (items: { src: string }[]) => {
items.forEach((item) => URL.revokeObjectURL(item.src));
const revokeSmartCutoutItem = (item: SmartCutoutImageItem | null) => {
if (!item) return;
URL.revokeObjectURL(item.src);
if (item.originalSrc && item.originalSrc !== item.src) URL.revokeObjectURL(item.originalSrc);
};
const revokeSmartCutoutItems = (items: SmartCutoutImageItem[]) => {
items.forEach(revokeSmartCutoutItem);
};
const clearSmartCutoutTransition = () => {
@@ -1695,9 +1703,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
return [];
});
setSmartCutoutImage((current) => {
if (current?.src) URL.revokeObjectURL(current.src);
revokeSmartCutoutItem(current);
return null;
});
setIsSmartCutoutComparing(false);
setComposerMenu(null);
};
@@ -2050,6 +2059,21 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
if (!selectedDetailModules.length) setSelectedDetailModules(defaultCloneDetailModuleIds);
};
const openHotVideoPage = () => {
clearSmartCutoutTransition();
setActiveQuickTool("hot-video");
setComposerMenu(null);
setIsCloneSettingsCollapsed(true);
setIsCommandHistoryCollapsed(true);
};
const closeHotVideoPage = () => {
setActiveQuickTool(null);
setComposerMenu(null);
setIsCloneSettingsCollapsed(false);
setIsCommandHistoryCollapsed(false);
};
const closeSmartCutoutTool = () => {
runSmartCutoutPageTransition(
{
@@ -2062,9 +2086,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
return [];
});
setSmartCutoutImage((current) => {
if (current?.src) URL.revokeObjectURL(current.src);
revokeSmartCutoutItem(current);
return null;
});
setIsSmartCutoutComparing(false);
setActiveQuickTool(null);
setComposerMenu(null);
},
@@ -2087,9 +2112,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
return [];
});
setSmartCutoutImage((current) => {
if (current?.src) URL.revokeObjectURL(current.src);
revokeSmartCutoutItem(current);
return null;
});
setIsSmartCutoutComparing(false);
},
);
};
@@ -2106,10 +2132,14 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
return [];
});
setSmartCutoutImage((current) => {
if (current?.src) URL.revokeObjectURL(current.src);
revokeSmartCutoutItem(current);
return null;
});
const nextImages = imageFiles.map((file) => ({ src: URL.createObjectURL(file), name: file.name }));
setIsSmartCutoutComparing(false);
const nextImages = imageFiles.map((file) => {
const originalSrc = URL.createObjectURL(file);
return { src: originalSrc, originalSrc, name: file.name };
});
smartCutoutPendingUrlsRef.current = nextImages.map((item) => item.src);
setActiveQuickTool("cutout");
setSmartCutoutSizeKey("original");
@@ -2154,17 +2184,30 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
[smartCutoutSizeKey],
);
const previewSmartCutoutSize = isSmartCutoutComparing ? smartCutoutSizeOptions[0] : selectedSmartCutoutSize;
const previewSmartCutoutSizeKey = isSmartCutoutComparing ? "original" : smartCutoutSizeKey;
const previewSmartCutoutImageSrc = isSmartCutoutComparing ? smartCutoutImage?.originalSrc ?? smartCutoutImage?.src : smartCutoutImage?.src;
const smartCutoutFrameStyle = useMemo<CSSProperties>(
() => ({
"--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,
"--smart-cutout-frame-width": previewSmartCutoutSize.frameWidth,
"--smart-cutout-frame-aspect": previewSmartCutoutSize.frameAspect,
"--smart-cutout-image-max-width": previewSmartCutoutSize.imageMaxWidth,
"--smart-cutout-image-max-height": previewSmartCutoutSize.imageMaxHeight,
} as CSSProperties),
[selectedSmartCutoutSize, smartCutoutBackgroundValue],
[previewSmartCutoutSize, smartCutoutBackgroundValue],
);
const showSmartCutoutOriginalCompare = (event: ReactPointerEvent<HTMLButtonElement>) => {
event.currentTarget.setPointerCapture(event.pointerId);
setIsSmartCutoutComparing(true);
};
const hideSmartCutoutOriginalCompare = () => {
setIsSmartCutoutComparing(false);
};
const applySmartCutoutHsv = (h: number, s: number, v: number) => {
const rgb = hsvToRgb(h, s, v);
setSmartCutoutBackgroundColor(rgbToHex(rgb.r, rgb.g, rgb.b));
@@ -2221,9 +2264,14 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const aspect = parseSmartCutoutAspect(selectedSmartCutoutSize.frameAspect);
const naturalWidth = Math.max(1, image.naturalWidth || image.width || 1200);
const naturalHeight = Math.max(1, image.naturalHeight || image.height || 900);
const outputWidth = "outputWidth" in selectedSmartCutoutSize ? selectedSmartCutoutSize.outputWidth : undefined;
const outputHeight = "outputHeight" in selectedSmartCutoutSize ? selectedSmartCutoutSize.outputHeight : undefined;
let canvasWidth = naturalWidth;
let canvasHeight = naturalHeight;
if (aspect) {
if (outputWidth && outputHeight) {
canvasWidth = outputWidth;
canvasHeight = outputHeight;
} else if (aspect) {
const longSide = 1600;
if (aspect >= 1) {
canvasWidth = longSide;
@@ -3413,6 +3461,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const isQuickDetailTool = isCloneTool && activeQuickTool === "detail";
const isWatermarkTool = isCloneTool && activeQuickTool === "watermark";
const isImageEditTool = isCloneTool && activeQuickTool === "image-edit";
const isHotVideoTool = isCloneTool && activeQuickTool === "hot-video";
const pageLabel = isSetTool ? "鍟嗗搧濂楀浘" : isDetail ? "A+/璇︽儏椤?" : isTryOn ? "AI鏈嶉グ绌挎埓" : activeToolMeta?.label || "鍟嗗搧宸ュ叿";
const setPrimaryLabel =
setImages.length === 0
@@ -4464,7 +4513,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
{ label: "变清晰", icon: <FileImageOutlined /> },
{ label: "AI消除", icon: <SettingOutlined /> },
{ label: "证件照", icon: <SkinOutlined /> },
{ label: "爆款视频", icon: <CloudUploadOutlined /> },
{ label: "爆款视频", icon: <CloudUploadOutlined />, onClick: openHotVideoPage },
{ label: "拼图", icon: <TableOutlined /> },
].map((item) => (
<button
@@ -4560,31 +4609,48 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
<section className="ecom-smart-editor">
<div className="ecom-smart-editor__workspace">
<div className="ecom-smart-editor__canvas">
<div className={`ecom-smart-editor__checker is-size-${smartCutoutSizeKey}`} style={smartCutoutFrameStyle}>
<div className={`ecom-smart-editor__checker is-size-${previewSmartCutoutSizeKey}`} style={smartCutoutFrameStyle}>
<div className="ecom-smart-editor__background-layer" aria-hidden="true" />
<img src={smartCutoutImage.src} alt={smartCutoutImage.name} />
<img src={previewSmartCutoutImageSrc ?? smartCutoutImage.src} alt={smartCutoutImage.name} />
</div>
<div className="ecom-smart-editor__canvas-actions">
<button type="button"></button>
<button type="button"></button>
<button
type="button"
onPointerDown={showSmartCutoutOriginalCompare}
onPointerUp={hideSmartCutoutOriginalCompare}
onPointerCancel={hideSmartCutoutOriginalCompare}
onBlur={hideSmartCutoutOriginalCompare}
onKeyDown={(event) => {
if (event.key === " " || event.key === "Enter") setIsSmartCutoutComparing(true);
}}
onKeyUp={hideSmartCutoutOriginalCompare}
>
</button>
</div>
</div>
<div className="ecom-smart-editor__tools-shell">
<strong className="ecom-smart-editor__tools-title"></strong>
<button type="button" className="ecom-smart-editor__tools-nav" onClick={() => scrollSmartCutoutTools(-1)} aria-label="查看上一组尺寸">
</button>
<div className="ecom-smart-editor__tools" ref={smartCutoutToolsRef}>
{smartCutoutSizeOptions.map((item) => (
<button
key={item.key}
type="button"
className={smartCutoutSizeKey === item.key ? "is-active" : ""}
onClick={() => setSmartCutoutSizeKey(item.key)}
aria-pressed={smartCutoutSizeKey === item.key}
>
<span className={`ecom-smart-editor__tool-icon ecom-smart-editor__tool-icon--${item.icon}`} aria-hidden="true" />
<span>{item.label}</span>
</button>
<div className="ecom-smart-editor__tool-item" key={item.key}>
<button
type="button"
className={smartCutoutSizeKey === item.key ? "is-active" : ""}
onClick={() => setSmartCutoutSizeKey(item.key)}
aria-label={`${item.label}${"sizeLabel" in item ? ` ${item.sizeLabel}` : ""}`}
aria-pressed={smartCutoutSizeKey === item.key}
>
<span className={`ecom-smart-editor__tool-icon ecom-smart-editor__tool-icon--${item.icon}`} aria-hidden="true" />
</button>
<span className="ecom-smart-editor__tool-text">
<span>{item.label}</span>
{"sizeLabel" in item ? <span>{item.sizeLabel}</span> : null}
</span>
</div>
))}
</div>
<button type="button" className="ecom-smart-editor__tools-nav" onClick={() => scrollSmartCutoutTools(1)} aria-label="查看更多尺寸">
@@ -5151,6 +5217,170 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
</div>
);
const hotVideoPreview = (
<main className="ecom-hot-video-page" aria-label="爆款视频">
<nav className="ecom-hot-video-nav">
<button type="button" className="ecom-hot-video-back" onClick={closeHotVideoPage}>
</button>
<div className="ecom-hot-video-nav-title">
<h1></h1>
<span>AI智能策划 · </span>
</div>
<div className="ecom-hot-video-nav-meta">
<span>{platform} / {formatRatioDisplayValue(ratio)} / {cloneVideoDuration} / {cloneVideoQuality === "standard" ? "720P" : "1080P"}</span>
</div>
</nav>
<div className="ecom-hot-video-body">
<aside className="ecom-hot-video-settings" aria-label="视频设置">
<section className="ecom-hot-video-section">
<strong></strong>
<div
role="button"
tabIndex={0}
className={`ecom-hot-video-upload${productImages.length ? " has-images" : ""}${isProductUploadDragging ? " is-dragging" : ""}`}
onClick={() => quickProductInputRef.current?.click()}
onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") quickProductInputRef.current?.click(); }}
onDragOver={(event) => { event.preventDefault(); setIsProductUploadDragging(true); }}
onDragLeave={(event) => { event.preventDefault(); setIsProductUploadDragging(false); }}
onDrop={handleProductDrop}
>
<CloudUploadOutlined />
<span></span>
<em> JPG / PNG / WebP</em>
{productImages.length > 0 ? (
<div className="ecom-hot-video-upload-thumbs">
{productImages.map((img) => (
<figure key={img.id}>
<img src={img.src} alt={img.name} />
<button
type="button"
aria-label="删除"
onClick={(event) => { event.stopPropagation(); removeProductImage(img.id); }}
>×</button>
</figure>
))}
</div>
) : null}
</div>
<input
ref={quickProductInputRef}
type="file"
accept="image/*"
multiple
className="ecom-command-hidden-file"
onChange={handleProductUpload}
/>
</section>
<section className="ecom-hot-video-section">
<strong></strong>
<textarea
className="ecom-hot-video-textarea"
placeholder="描述你的商品特点、卖点、使用场景等,帮助AI更好地策划视频内容..."
rows={4}
value={requirement}
onChange={(event) => setRequirement(event.target.value)}
maxLength={500}
/>
</section>
<section className="ecom-hot-video-section">
<strong></strong>
<div className="ecom-hot-video-options">
{platformOptions.map((option) => (
<button
key={option}
type="button"
className={platform === option ? "is-active" : ""}
onClick={() => setPlatform(option)}
>
{renderPlatformLogo(option)}
<span>{option}</span>
</button>
))}
</div>
</section>
<section className="ecom-hot-video-section">
<strong></strong>
<div className="ecom-hot-video-options ecom-hot-video-options--ratio">
{cloneRatioOptions.map((option) => (
<button
key={option}
type="button"
className={ratio === option ? "is-active" : ""}
onClick={() => setRatio(option)}
>
{formatRatioDisplayValue(option)}
</button>
))}
</div>
</section>
<section className="ecom-hot-video-section">
<strong></strong>
<div className="ecom-hot-video-options">
{cloneVideoQualityOptions.map((option) => (
<button
key={option.key}
type="button"
className={cloneVideoQuality === option.key ? "is-active" : ""}
onClick={() => setCloneVideoQuality(option.key)}
>
<strong>{option.label}</strong>
<span>{option.desc}</span>
</button>
))}
</div>
</section>
<section className="ecom-hot-video-section">
<strong> · {cloneVideoDuration}</strong>
<input
type="range"
className="ecom-hot-video-range"
min={cloneVideoDurationMin}
max={cloneVideoDurationMax}
step={5}
value={cloneVideoDuration}
onChange={(event) => setCloneVideoDuration(clampCloneVideoDuration(Number(event.target.value)))}
style={cloneVideoDurationStyle}
/>
<div className="ecom-hot-video-range-labels">
<span>{cloneVideoDurationMin}</span>
<span>{cloneVideoDurationMax}</span>
</div>
</section>
<button
type="button"
className="ecom-hot-video-start"
disabled={!productImages.length && !requirement.trim()}
onClick={() => setVideoPlanTrigger((prev) => prev + 1)}
>
</button>
</aside>
<section className="ecom-hot-video-workspace">
<EcommerceVideoWorkspace
isAuthenticated={isAuthenticated}
productImageDataUrls={ecommerceVideoImageDataUrls}
productImageFiles={ecommerceVideoImageFiles}
requirement={requirement}
platform={platform}
aspectRatio={ratio.includes("916") || ratio.includes("9:16") ? "9:16" : ratio.includes("169") || ratio.includes("16:9") ? "16:9" : ratio.includes("34") || ratio.includes("3:4") ? "3:4" : "9:16"}
durationSeconds={cloneVideoDuration}
resolution={cloneVideoQuality === "standard" ? "720P" : "1080P"}
onRequestLogin={() => (isAuthenticated ? undefined : requestLogin())}
onOpenHistory={() => setVideoHistoryVisible(true)}
triggerPlan={videoPlanTrigger}
/>
</section>
</div>
</main>
);
const quickProductSetPreview = (
<main className={`ecom-quick-set-page${isQuickPanelCollapsed ? " is-panel-collapsed" : ""}`} aria-label="AI商品套图">
<div className="ecom-quick-set-body">
@@ -5587,20 +5817,22 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
? quickProductSetPreview
: isQuickDetailTool
? quickDetailPreview
: cloneOutput === "video-outfit" && results.length > 0 && results[0].type === "video"
? (
<main className="product-clone-preview product-clone-preview--video-outfit" style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
<div style={{ maxWidth: "100%", maxHeight: "100%" }}>
<video src={results[0].src} controls style={{ maxWidth: "100%", maxHeight: "70vh", borderRadius: "12px" }} />
</div>
</main>
)
: clonePreview
: isHotVideoTool
? hotVideoPreview
: cloneOutput === "video-outfit" && results.length > 0 && results[0].type === "video"
? (
<main className="product-clone-preview product-clone-preview--video-outfit" style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
<div style={{ maxWidth: "100%", maxHeight: "100%" }}>
<video src={results[0].src} controls style={{ maxWidth: "100%", maxHeight: "70vh", borderRadius: "12px" }} />
</div>
</main>
)
: clonePreview
: placeholderPreview;
return (
<section
className={`product-clone-page page-motion${isCloneTool && isCloneSettingsCollapsed ? " is-settings-collapsed" : ""}${isCloneTool && isCommandHistoryCollapsed ? " is-history-collapsed" : ""}${isSmartCutoutTool ? " is-smart-cutout-page" : ""}${isQuickSetTool || isQuickDetailTool ? " is-quick-set-page" : ""}${isWatermarkTool ? " is-watermark-page" : ""}${isImageEditTool ? " is-image-workbench-page" : ""}`}
className={`product-clone-page page-motion${isCloneTool && isCloneSettingsCollapsed ? " is-settings-collapsed" : ""}${isCloneTool && isCommandHistoryCollapsed ? " is-history-collapsed" : ""}${isSmartCutoutTool ? " is-smart-cutout-page" : ""}${isQuickSetTool || isQuickDetailTool ? " is-quick-set-page" : ""}${isWatermarkTool ? " is-watermark-page" : ""}${isImageEditTool ? " is-image-workbench-page" : ""}${isHotVideoTool ? " is-hot-video-page" : ""}`}
data-tool={activeTool}
aria-label={pageLabel}
>
@@ -5624,7 +5856,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
{isSetTool ? setPanel : isDetail ? detailPanel : isTryOn ? tryOnPanel : isCloneTool ? clonePanel : placeholderPanel}
</aside>
{isCloneTool && !isSmartCutoutTool && !isQuickSetTool && !isQuickDetailTool && !isWatermarkTool && !isImageEditTool ? (
{isCloneTool && !isSmartCutoutTool && !isQuickSetTool && !isQuickDetailTool && !isWatermarkTool && !isImageEditTool && !isHotVideoTool ? (
<button
type="button"
className="clone-ai-settings-toggle"