feat(ecommerce): add one-click copywriting tool with quick-board entry

- Add EcommerceCopywritingPanel component

- Wire copywriting tool into EcommercePage routing and state

- Add quick action entry; place before '更多功能'

- Add copywriting styles aligned with quick-set/hot-clone pages

- Merge latest main
This commit is contained in:
Codex
2026-06-16 21:47:07 +08:00
parent 30222cd830
commit ad38a4a0e3
3 changed files with 1606 additions and 300 deletions
+406 -7
View File
@@ -39,6 +39,7 @@ import EcommerceDetailPanel from "./panels/EcommerceDetailPanel";
import EcommerceSetPanel from "./panels/EcommerceSetPanel";
import EcommerceTryOnPanel from "./panels/EcommerceTryOnPanel";
import EcommerceClonePanel from "./panels/EcommerceClonePanel";
import EcommerceCopywritingPanel from "./panels/EcommerceCopywritingPanel";
import { ecommerceOssScopes, saveUnifiedEcommerceGenerationRecord, deleteEcommerceGenerationRecord } from "./ecommerceGenerationPersistence";
import { downloadResultAsset } from "../workbench/workbenchDownload";
@@ -1712,7 +1713,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const [selectedProductSetPreview, setSelectedProductSetPreview] = useState<ProductSetPreviewSelection | null>(null);
const [showHostingModal, setShowHostingModal] = useState(false);
const [productImages, setProductImages] = useState<CloneImageItem[]>([]);
const [activeQuickTool, setActiveQuickTool] = useState<"cutout" | "detail" | "watermark" | "image-edit" | "translate" | "hot" | null>(null);
const [activeQuickTool, setActiveQuickTool] = useState<"cutout" | "detail" | "watermark" | "image-edit" | "translate" | "hot" | "quick-set" | "copywriting" | null>(null);
const [smartCutoutImage, setSmartCutoutImage] = useState<SmartCutoutImageItem | null>(null);
const [smartCutoutBatchImages, setSmartCutoutBatchImages] = useState<SmartCutoutImageItem[]>([]);
const [smartCutoutBackgroundColor, setSmartCutoutBackgroundColor] = useState("#ffffff");
@@ -1786,6 +1787,11 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const [cloneVideoSmart, setCloneVideoSmart] = useState(true);
const [isCloneSettingsCollapsed, setIsCloneSettingsCollapsed] = useState(false);
const [isCloneConversationCollapsed, setIsCloneConversationCollapsed] = useState(false);
const [quickSetStatus, setQuickSetStatus] = useState<"idle" | "generating" | "done" | "failed">("idle");
const [quickSetResultUrls, setQuickSetResultUrls] = useState<string[]>([]);
const [quickSetProgress, setQuickSetProgress] = useState(0);
const [quickSetRequirement, setQuickSetRequirement] = useState("");
const quickSetProgressRef = useRef<number | null>(null);
const [previewZoom, setPreviewZoom] = useState(1);
const quickSetSelectTimerRef = useRef<number | null>(null);
const openQuickSetSelectRef = useRef<CloneBasicSelectKey | null>(null);
@@ -2271,6 +2277,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const canGenerateTryOn = garmentImages.length > 0 && tryOnStatus !== "generating" && tryOnStatus !== "modeling";
const canGenerateDetail = detailProductImages.length > 0 && detailStatus !== "generating";
const canGenerateHot = cloneReferenceImages.length > 0 && hotStatus !== "generating";
const canGenerateQuickSet = productImages.length > 0 && quickSetStatus !== "generating";
const cloneVideoDurationProgress =
((cloneVideoDuration - cloneVideoDurationMin) / (cloneVideoDurationMax - cloneVideoDurationMin)) * 100;
const cloneVideoDurationStyle: CSSProperties = useMemo(
@@ -4608,6 +4615,12 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
);
};
const handleQuickSetAiWrite = () => {
setQuickSetRequirement(
"1.产品名称:无线降噪蓝牙耳机\n2.核心卖点:主动降噪、24H续航、低延迟连接、舒适佩戴\n3.适用人群:通勤、办公、运动和旅行用户\n4.期望场景:地铁通勤、居家办公、户外运动\n5.具体参数:蓝牙5.3、IPX4防水、快充10分钟使用2小时",
);
};
const stopHotProgress = () => {
if (hotProgressRef.current !== null) {
window.clearInterval(hotProgressRef.current);
@@ -4615,6 +4628,27 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
}
};
const stopQuickSetProgress = () => {
if (quickSetProgressRef.current !== null) {
window.clearInterval(quickSetProgressRef.current);
quickSetProgressRef.current = null;
}
};
const startQuickSetProgress = () => {
stopQuickSetProgress();
setQuickSetProgress(0);
quickSetProgressRef.current = window.setInterval(() => {
setQuickSetProgress((prev) => {
if (prev >= 90) {
stopQuickSetProgress();
return 90;
}
return prev + (90 - prev) * 0.06;
});
}, 500);
};
const startHotProgress = () => {
stopHotProgress();
setHotProgress(0);
@@ -4652,6 +4686,42 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
);
};
const handleQuickSetGenerate = () => {
if (!canGenerateQuickSet) return;
imageAbortRef.current = { current: false };
lastFailedActionRef.current = null;
startQuickSetProgress();
setQuickSetStatus("generating");
void generateSetImages(
productImages, cloneSetCounts, quickSetRequirement,
platform, ratio, language, market,
(s) => {
setQuickSetStatus(s as ProductCloneStatus);
if (s === "done") {
stopQuickSetProgress();
setQuickSetProgress(100);
} else if (s === "failed") {
stopQuickSetProgress();
setQuickSetProgress(0);
}
},
(urls) => {
setQuickSetResultUrls(urls);
const validUrls = urls.filter(Boolean);
if (validUrls.length) {
setQuickSetStatus("done");
stopQuickSetProgress();
setQuickSetProgress(100);
} else {
setQuickSetStatus("failed");
stopQuickSetProgress();
setQuickSetProgress(0);
}
},
);
lastFailedActionRef.current = () => handleQuickSetGenerate();
};
const handleHotMaterialMouseEnter = (src: string, event: ReactMouseEvent<HTMLElement>) => {
const rect = event.currentTarget.getBoundingClientRect();
const previewHalfWidth = 150;
@@ -4716,6 +4786,37 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
setComposerMenu(null);
};
const openQuickSetPage = () => {
clearSmartCutoutTransition();
setActiveQuickTool("quick-set");
setComposerMenu(null);
setIsCloneSettingsCollapsed(false);
setIsQuickPanelCollapsed(false);
};
const closeQuickSetPage = () => {
stopQuickSetProgress();
setActiveQuickTool(null);
setQuickSetStatus("idle");
setQuickSetResultUrls([]);
setQuickSetProgress(0);
setQuickSetRequirement("");
setComposerMenu(null);
};
const openCopywritingPage = () => {
clearSmartCutoutTransition();
setActiveQuickTool("copywriting");
setComposerMenu(null);
setIsCloneSettingsCollapsed(false);
setIsQuickPanelCollapsed(false);
};
const closeCopywritingPage = () => {
setActiveQuickTool(null);
setComposerMenu(null);
};
const resetTask = () => {
setSetImages([]);
setProductSetRequirement("");
@@ -4779,6 +4880,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const isImageEditTool = isCloneTool && activeQuickTool === "image-edit";
const isTranslateTool = isCloneTool && activeQuickTool === "translate";
const isHotCloneTool = isCloneTool && activeQuickTool === "hot";
const isQuickSetTool = isCloneTool && activeQuickTool === "quick-set";
const isCopywritingTool = isCloneTool && activeQuickTool === "copywriting";
const pageLabel = isSetTool ? "商品套图" : isDetail ? "A+/详情页" : isTryOn ? "AI服饰穿戴" : activeToolMeta?.label || "商品工具";
const setPrimaryLabel =
setImages.length === 0
@@ -5204,6 +5307,19 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
{ key: "ratio", label: "尺寸/比例", value: getQuickSetRatioValue(hotRatio), options: quickSetRatioOptions, onChange: setHotRatio },
];
const quickSetBasicSelects: Array<{
key: CloneBasicSelectKey;
label: string;
value: string;
options: string[];
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: "ratio", label: "尺寸/比例", value: getQuickSetRatioValue(ratio), options: quickSetRatioOptions, onChange: setRatio },
];
const cloneModelSelects: Array<{
key: CloneModelSelectKey;
label: string;
@@ -6229,15 +6345,23 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
{ label: "智能抠图", tone: "cutout", icon: <ScissorOutlined />, onClick: openSmartCutoutUpload },
{ label: "去除水印", tone: "watermark", icon: <ClearOutlined />, onClick: openWatermarkRemovalPage },
{ label: "图片翻译", tone: "translate", icon: <GlobalOutlined />, onClick: openImageTranslatePage },
{ label: "商品套图", tone: "product", icon: <AppstoreOutlined />, onClick: openQuickSetPage },
{ label: "一键文案", tone: "copywriting", icon: <EditOutlined />, onClick: openCopywritingPage },
{ label: "更多功能", tone: "more", icon: <SettingOutlined />, disabled: true },
].map((item) => (
<button
key={item.label}
type="button"
className={`ecom-command-quick-card ecom-command-quick-card--${item.tone}`}
disabled={item.disabled}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
item.onClick?.();
if (item.disabled) {
toast.info("更多功能即将上线,敬请期待!");
} else {
item.onClick?.();
}
}}
>
<span aria-hidden="true">{item.icon}</span>
@@ -7243,6 +7367,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const quickDetailVisibleSelect = quickDetailBasicSelects.find((item) => item.key === visibleQuickSetSelect) ?? null;
const quickHotVisibleSelect = quickHotBasicSelects.find((item) => item.key === visibleQuickSetSelect) ?? null;
const quickSetVisibleSelect = quickSetBasicSelects.find((item) => item.key === visibleQuickSetSelect) ?? null;
const quickDetailPreview = (
<main key="quick-detail" className={`ecom-quick-set-page ecom-quick-detail-page ecom-tool-page-enter${isQuickPanelCollapsed ? " is-panel-collapsed" : ""}`} aria-label="A+详情页生成">
@@ -7704,6 +7829,266 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
</main>
);
const quickSetGenPreview = (
<main key="quick-set" className={`ecom-quick-set-page ecom-quick-hot-page ecom-tool-page-enter${isQuickPanelCollapsed ? " is-panel-collapsed" : ""}`} aria-label="电商套图生成">
<div className="ecom-quick-set-body">
<aside className="ecom-quick-set-panel" aria-label="电商套图设置" onWheel={handleQuickPanelWheel}>
<header className="ecom-quick-set-panel-head">
<strong className="ecom-quick-set-page-title"></strong>
<button type="button" className="ecom-quick-set-back" onClick={closeQuickSetPage}></button>
<button type="button" className="ecom-quick-set-back" onClick={closeQuickSetPage}></button>
</header>
<section>
<strong><FileImageOutlined /> </strong>
{productImages.length ? (
<div
role="button"
tabIndex={0}
className={`ecom-quick-set-upload ecom-quick-hot-material has-images${isProductUploadDragging ? " is-dragging" : ""}`}
onClick={() => productInputRef.current?.click()}
onKeyDown={(event) => openQuickUploadWithKeyboard(event, productInputRef)}
onDragOver={(event) => { event.preventDefault(); event.stopPropagation(); if (event.dataTransfer.types.includes("Files")) setIsProductUploadDragging(true); }}
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)}
<button
type="button"
className="ecom-quick-hot-add-btn"
aria-label="添加更多素材"
onClick={(event) => {
event.stopPropagation();
productInputRef.current?.click();
}}
>
<PlusOutlined />
</button>
</div>
) : (
<div
role="button"
tabIndex={0}
className={`ecom-quick-set-upload ecom-quick-hot-material${isProductUploadDragging ? " is-dragging" : ""}`}
onClick={() => productInputRef.current?.click()}
onKeyDown={(event) => openQuickUploadWithKeyboard(event, productInputRef)}
onDragOver={(event) => { event.preventDefault(); event.stopPropagation(); if (event.dataTransfer.types.includes("Files")) setIsProductUploadDragging(true); }}
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); }}
>
<FileImageOutlined />
<span></span>
<em> 7 </em>
<b>+ </b>
</div>
)}
<input
ref={productInputRef}
type="file"
accept="image/*"
multiple
className="ecom-command-hidden-file"
onChange={handleProductUpload}
aria-label="上传商品图片"
/>
</section>
<section className="ecom-quick-set-basic-section">
<span className="ecom-quick-set-label"></span>
<div className="ecom-quick-set-select-anchor">
<div className="ecom-quick-set-selects">
{quickSetBasicSelects.map((item) => (
<button
key={item.key}
type="button"
className={openQuickSetSelect === item.key ? "is-active" : ""}
onClick={() => toggleQuickSetSelect(item.key)}
>
<span>{item.label}</span><strong>{formatRatioDisplayValue(item.value)}</strong><em></em>
</button>
))}
</div>
{quickSetVisibleSelect ? (
<div
className={`ecom-quick-set-dropdown ecom-quick-set-dropdown--${quickSetVisibleSelect.key}${isQuickSetSelectClosing ? " is-closing" : ""}`}
role="listbox"
aria-label={quickSetVisibleSelect.label}
>
{quickSetVisibleSelect.options.map((option) => (
<button
key={option}
type="button"
className={quickSetVisibleSelect.value === option ? "is-active" : ""}
onClick={() => {
quickSetVisibleSelect.onChange(option);
closeQuickSetSelect();
}}
>
{formatRatioDisplayValue(option)}
</button>
))}
</div>
) : null}
</div>
</section>
<section className="ecom-quick-set-count-section">
<span className="ecom-quick-set-label"></span>
<p className="ecom-quick-set-hint"> 1-16 </p>
<div className="ecom-quick-set-counts">
{cloneSetCountOptions.map((item) => {
const count = cloneSetCounts[item.key];
const decrementDisabled = count <= 0 || cloneSetTotal <= minCloneSetTotal;
const incrementDisabled = cloneSetTotal >= maxCloneSetTotal;
return (
<div key={item.key} className="ecom-quick-set-count-row">
<div className="ecom-quick-set-count-info">
<strong>{item.title}</strong>
<span>{item.desc}</span>
</div>
<div className="clone-ai-count-stepper" aria-label={`${item.title}数量`}>
<button
type="button"
disabled={decrementDisabled}
onPointerDown={(event) => {
event.preventDefault();
startCloneSetCountHold(item.key, -1, decrementDisabled);
}}
onPointerUp={clearCloneSetCountHold}
onPointerLeave={clearCloneSetCountHold}
onPointerCancel={clearCloneSetCountHold}
onBlur={clearCloneSetCountHold}
aria-label={`减少${item.title}`}
>
-
</button>
<b>{count}</b>
<button
type="button"
disabled={incrementDisabled}
onPointerDown={(event) => {
event.preventDefault();
startCloneSetCountHold(item.key, 1, incrementDisabled);
}}
onPointerUp={clearCloneSetCountHold}
onPointerLeave={clearCloneSetCountHold}
onPointerCancel={clearCloneSetCountHold}
onBlur={clearCloneSetCountHold}
aria-label={`增加${item.title}`}
>
+
</button>
</div>
</div>
);
})}
</div>
</section>
<section className="ecom-quick-hot-requirement">
<div className="ecom-quick-hot-requirement__head">
<strong> &amp; </strong>
<button type="button" className="ecom-quick-hot-requirement__ai" onClick={handleQuickSetAiWrite}>AI </button>
</div>
<div className="ecom-quick-hot-requirement__input">
<textarea
value={quickSetRequirement}
onChange={(event) => setQuickSetRequirement(event.target.value.slice(0, 500))}
placeholder="建议包含以下信息:产品名称、核心卖点、期望场景、具体参数"
maxLength={500}
/>
<span>{quickSetRequirement.length}/500</span>
</div>
</section>
<div className="ecom-quick-hot-actions">
<button type="button" className="ecom-quick-set-primary ecom-quick-hot-generate" onClick={handleQuickSetGenerate} disabled={!canGenerateQuickSet}>
{quickSetStatus === "generating" ? <LoadingOutlined /> : "✦"}
</button>
<button
type="button"
className={`ecom-quick-set-primary ecom-quick-set-primary--cancel${quickSetStatus !== "generating" ? " is-disabled" : ""}`}
onClick={quickSetStatus === "generating" ? handleCancelGenerate : undefined}
disabled={quickSetStatus !== "generating"}
>
</button>
</div>
</aside>
<section className="ecom-quick-set-stage">
<header className="ecom-quick-set-preview-head">
<h1></h1>
<p>AI <span></span> </p>
<div>
<button type="button" onClick={() => setPreviewZoom((value) => Math.max(0.25, value - 0.1))}>-</button>
<strong>{Math.round(previewZoom * 100)}%</strong>
<button type="button" onClick={() => setPreviewZoom((value) => Math.min(2, value + 0.1))}>+</button>
</div>
</header>
<div className="ecom-quick-set-canvas" onWheel={handleQuickPreviewWheel}>
{quickSetStatus === "done" && quickSetResultUrls.length > 0 ? (
<section className="ecom-quick-detail-result" style={{ transform: `scale(${previewZoom})` }}>
<div className="ecom-quick-set-result-grid">
{quickSetResultUrls.map((url, index) => (
<figure key={`quick-set-${index}`}>
<img src={url} alt={`套图 ${index + 1}`} />
<span> {index + 1}</span>
</figure>
))}
</div>
<button
type="button"
className="ecom-quick-detail-download"
onClick={() => {
quickSetResultUrls.forEach((url, index) => {
const link = document.createElement("a");
link.href = url;
link.download = `电商套图-${index + 1}-${Date.now()}.png`;
document.body.appendChild(link);
link.click();
link.remove();
});
}}
>
<CloudUploadOutlined />
</button>
</section>
) : quickSetStatus === "generating" ? (
<section className="ecom-quick-set-generating">
<LoadingOutlined />
<strong></strong>
<span>AI ...</span>
<div className="ecom-quick-set-progress">
<div className="ecom-quick-set-progress-bar" style={{ width: `${Math.round(quickSetProgress)}%` }} />
</div>
<em className="ecom-quick-set-progress-text">{Math.round(quickSetProgress)}%</em>
</section>
) : quickSetStatus === "failed" ? (
<section className="ecom-quick-set-failed">
<FrownOutlined />
<strong></strong>
<span></span>
<button type="button" onClick={handleQuickSetGenerate} disabled={!canGenerateQuickSet}></button>
</section>
) : productImages.length ? (
<section className="ecom-quick-detail-preview-card" style={{ transform: `scale(${previewZoom})` }}>
{detailGridSamples.slice(0, 6).map((src, index) => (
<figure key={src}>
<img src={src} alt={`套图预览 ${index + 1}`} />
<span>{detailModules[index]?.title ?? "套图模块"}</span>
</figure>
))}
</section>
) : (
<section className="ecom-quick-set-empty">
<FileImageOutlined />
<strong></strong>
<span>AI </span>
</section>
)}
</div>
</section>
</div>
<button type="button" className="ecom-quick-set-help" aria-label="帮助" onClick={() => toast.info("上传商品图后,选择平台和套图数量即可生成电商套图。")}>?</button>
</main>
);
const detailPreview = (
<main className="product-clone-preview product-clone-preview--detail" aria-label="A+详情预览" onWheel={handlePreviewWheel}>
<div className="product-clone-preview__headline">
@@ -7799,6 +8184,12 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
</main>
);
const copywritingPreview = (
<div key="copywriting" className="ecom-quick-page-wrap ecom-tool-page-enter">
<EcommerceCopywritingPanel onClose={closeCopywritingPage} />
</div>
);
const activePreview = isSetTool
? setPreview
: isDetail
@@ -7826,9 +8217,17 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
{hotClonePreview}
</div>
)
: clonePreview
: isQuickSetTool
? (
<div key={`quick-${activeQuickTool}`} className="ecom-quick-page-wrap ecom-tool-page-enter">
{quickSetGenPreview}
</div>
)
: isCopywritingTool
? copywritingPreview
: clonePreview
: placeholderPreview;
const isMainCloneWorkspace = isCloneTool && !isSmartCutoutTool && !isQuickDetailTool && !isWatermarkTool && !isTranslateTool && !isImageEditTool;
const isMainCloneWorkspace = isCloneTool && !isSmartCutoutTool && !isQuickDetailTool && !isWatermarkTool && !isTranslateTool && !isImageEditTool && !isQuickSetTool && !isCopywritingTool;
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;
@@ -7864,7 +8263,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
return (
<section
className={`product-clone-page page-motion${isCloneTool && isCloneSettingsCollapsed ? " is-settings-collapsed" : ""}${isCloneTool && isCommandHistoryCollapsed ? " is-history-collapsed" : ""}${isRecordDetailWorkspace && isCloneConversationCollapsed ? " is-conversation-collapsed" : ""}${isRecordDetailWorkspace ? " is-history-detail" : ""}${isSmartCutoutTool ? " is-smart-cutout-page" : ""}${isQuickDetailTool ? " is-quick-set-page" : ""}${isWatermarkTool ? " is-watermark-page" : ""}${isTranslateTool ? " is-translate-page" : ""}${isImageEditTool ? " is-image-workbench-page" : ""}${isHotCloneTool ? " is-hot-clone-page" : ""}`}
className={`product-clone-page page-motion${isCloneTool && isCloneSettingsCollapsed ? " is-settings-collapsed" : ""}${isCloneTool && isCommandHistoryCollapsed ? " is-history-collapsed" : ""}${isRecordDetailWorkspace && isCloneConversationCollapsed ? " is-conversation-collapsed" : ""}${isRecordDetailWorkspace ? " is-history-detail" : ""}${isSmartCutoutTool ? " is-smart-cutout-page" : ""}${isQuickDetailTool ? " is-quick-set-page" : ""}${isWatermarkTool ? " is-watermark-page" : ""}${isTranslateTool ? " is-translate-page" : ""}${isImageEditTool ? " is-image-workbench-page" : ""}${isHotCloneTool ? " is-hot-clone-page" : ""}${isQuickSetTool ? " is-quick-set-page" : ""}${isCopywritingTool ? " is-copywriting-page" : ""}`}
data-tool={activeTool}
aria-label={pageLabel}
>
@@ -7885,10 +8284,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
aria-label={`${pageLabel}参数`}
aria-hidden={isCloneTool && isCloneSettingsCollapsed ? true : undefined}
>
{isSetTool ? setPanel : isDetail ? detailPanel : isTryOn ? tryOnPanel : isCloneTool ? clonePanel : placeholderPanel}
{isSetTool ? setPanel : isDetail ? detailPanel : isTryOn ? tryOnPanel : isCopywritingTool ? placeholderPanel : isCloneTool ? clonePanel : placeholderPanel}
</aside>
{isCloneTool && !isSmartCutoutTool && !isQuickDetailTool && !isWatermarkTool && !isTranslateTool && !isImageEditTool && !isHotCloneTool ? (
{isCloneTool && !isSmartCutoutTool && !isQuickDetailTool && !isWatermarkTool && !isTranslateTool && !isImageEditTool && !isHotCloneTool && !isQuickSetTool && !isCopywritingTool ? (
<button
type="button"
className="clone-ai-settings-toggle"