8 Commits

35 changed files with 1351 additions and 200 deletions
+43
View File
@@ -0,0 +1,43 @@
# 自动检测文本文件并统一换行符
* text=auto eol=lf
# 源码强制使用 LF(跨平台一致)
*.ts text eol=lf
*.tsx text eol=lf
*.js text eol=lf
*.jsx text eol=lf
*.mjs text eol=lf
*.cjs text eol=lf
*.json text eol=lf
*.css text eol=lf
*.html text eol=lf
*.md text eol=lf
*.svg text eol=lf
# 配置类(统一 LF
*.yml text eol=lf
*.yaml text eol=lf
*.toml text eol=lf
*.conf text eol=lf
# Windows 专用脚本保持 CRLF
*.bat text eol=crlf
*.cmd text eol=crlf
# 二进制文件,不做换行符转换
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.webp binary
*.ico binary
*.woff binary
*.woff2 binary
*.ttf binary
*.eot binary
*.otf binary
*.mp4 binary
*.mp3 binary
*.pdf binary
*.zip binary
*.gz binary
+1
View File
@@ -0,0 +1 @@
export * from "./aiGenerationClient.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./apiErrorUtils.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./generationRecordClient.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./serverConnection.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./taskSubscription.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./webGenerationGateway.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./toastStore.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./ossAssets.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./workflows.ts";
+2
View File
@@ -0,0 +1,2 @@
export { default } from "./EcommercePage.tsx";
export * from "./EcommercePage.tsx";
+663 -88
View File
@@ -1,4 +1,4 @@
import {
import {
AppstoreOutlined,
ClearOutlined,
CloudUploadOutlined,
@@ -16,6 +16,7 @@
MenuFoldOutlined,
MenuUnfoldOutlined,
PaperClipOutlined,
PlusOutlined,
QuestionCircleOutlined,
ReloadOutlined,
ScissorOutlined,
@@ -281,6 +282,12 @@ type CloneBasicSelectKey = "platform" | "market" | "language" | "ratio";
type CloneModelSelectKey = "gender" | "age" | "ethnicity" | "body";
type CloneReferenceMode = "upload" | "link";
type CloneReplicateLevelKey = "style" | "high";
type CloneTemplateAsset = {
id: string;
title: string;
prompt: string;
mediaUrl: string;
};
type TryOnModelSource = "ai" | "library";
type TryOnStatus = "idle" | "modeling" | "ready" | "generating" | "done" | "failed";
type DetailStatus = "idle" | "ready" | "generating" | "done" | "failed";
@@ -1001,10 +1008,115 @@ const productSetOutputOptions: Array<{ key: ProductSetOutputKey; label: string;
{ key: "model", label: "模特图", desc: "真人穿搭展示", icon: <SkinOutlined /> },
{ key: "video", label: "短视频", desc: "分镜视频链路", icon: <VideoCameraOutlined /> },
];
const cloneOutputOptions: Array<{ key: CloneOutputKey; label: string; desc: string; icon: ReactNode }> = [
const cloneOutputOptions: Array<{ key: ProductSetOutputKey; label: string; desc: string; icon: ReactNode }> = [
...productSetOutputOptions,
{ key: "hot", label: "爆款复刻", desc: "参考图风格迁移", icon: <FireOutlined /> },
];
const cloneTemplateCards: Record<Exclude<CloneOutputKey, "hot">, CloneTemplateAsset[]> = {
set: [
{
id: "set-main",
title: "商品套图主图",
prompt: "生成一组统一风格的商品套图,包含主图、卖点图、场景图和细节图,主体清晰,色调统一,符合电商平台展示规范。",
mediaUrl: ossAssets.ecommerce.productSet.main,
},
{
id: "set-scene",
title: "商品套图场景",
prompt: "生成生活化场景商品套图,突出商品在真实环境中的使用感、氛围感和转化卖点。",
mediaUrl: ossAssets.ecommerce.productSet.scene,
},
{
id: "set-detail",
title: "商品套图细节",
prompt: "生成突出材质、工艺和边缘细节的商品套图,画面干净,信息聚焦,适合电商详情展示。",
mediaUrl: ossAssets.ecommerce.productSet.detail,
},
{
id: "set-selling",
title: "商品套图卖点",
prompt: "生成强调核心卖点和对比优势的商品套图,信息层级清晰,适合列表页和转化场景。",
mediaUrl: ossAssets.ecommerce.productSet.selling,
},
],
detail: [
{
id: "detail-hero",
title: "详情图头图",
prompt: "生成适用于 A+ 详情页的头图模块,突出品牌感、主卖点和视觉中心,版式清晰高级。",
mediaUrl: ossAssets.ecommerce.detail.longPage,
},
{
id: "detail-grid-a",
title: "详情图模块 A",
prompt: "生成模块化详情长图,重点展示产品卖点、功能说明和适用场景,适合滚动阅读。",
mediaUrl: ossAssets.ecommerce.detail.gridA,
},
{
id: "detail-grid-b",
title: "详情图模块 B",
prompt: "生成模块化详情长图,强化材质、规格和使用说明,视觉简洁,信息明确。",
mediaUrl: ossAssets.ecommerce.detail.gridB,
},
{
id: "detail-grid-c",
title: "详情图模块 C",
prompt: "生成模块化详情页内容,突出品牌叙事、细节拆解和购买理由,保持统一排版。",
mediaUrl: ossAssets.ecommerce.detail.gridC,
},
],
model: [
{
id: "model-dress-a",
title: "模特图穿搭 A",
prompt: "生成真人模特穿搭展示图,突出服装版型、上身效果和整体气质,姿态自然。",
mediaUrl: ossAssets.ecommerce.tryOn.dressA,
},
{
id: "model-dress-b",
title: "模特图穿搭 B",
prompt: "生成适合商品展示的模特图,强调衣型、垂感和真实穿着效果,画面干净。",
mediaUrl: ossAssets.ecommerce.tryOn.dressB,
},
{
id: "model-woman",
title: "模特图女模",
prompt: "生成自然站姿的女模特展示图,适合服饰、配件和穿搭类商品展示。",
mediaUrl: ossAssets.ecommerce.tryOn.modelWoman,
},
{
id: "model-man",
title: "模特图男模",
prompt: "生成真实感更强的男模特展示图,突出上身效果、轮廓和场景氛围。",
mediaUrl: ossAssets.ecommerce.tryOn.modelMan,
},
],
video: [
{
id: "video-hook",
title: "短视频开场",
prompt: "生成适合电商短视频的开场镜头,节奏明确,第一秒就突出产品和核心看点。",
mediaUrl: ossAssets.ecommerce.inspiration.tiktokPreference,
},
{
id: "video-scene",
title: "短视频场景",
prompt: "生成生活化使用场景的短视频分镜,画面连贯,围绕商品使用过程展开。",
mediaUrl: ossAssets.ecommerce.inspiration.officeStyleSet,
},
{
id: "video-review",
title: "短视频口播",
prompt: "生成适合口播讲解的电商短视频结构,包含产品亮点、卖点说明和收尾引导。",
mediaUrl: ossAssets.ecommerce.inspiration.asinListing,
},
{
id: "video-conversion",
title: "短视频转化",
prompt: "生成以转化为目标的短视频分镜,强化开头钩子、卖点展示和行动引导。",
mediaUrl: ossAssets.ecommerce.inspiration.competitorListing,
},
],
};
const cloneSetCountOptions: Array<{
key: CloneSetCountKey;
title: string;
@@ -1385,6 +1497,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const garmentInputRef = useRef<HTMLInputElement>(null);
const detailInputRef = useRef<HTMLInputElement>(null);
const detailProgressRef = useRef<number | null>(null);
const hotProgressRef = useRef<number | null>(null);
const hotMaterialInputRef = useRef<HTMLInputElement>(null);
const countHoldTimeoutRef = useRef<number | null>(null);
const countHoldIntervalRef = useRef<number | null>(null);
const isAuthenticated = Boolean((_props as Record<string, unknown>).isAuthenticated);
@@ -1418,7 +1532,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" | null>(null);
const [activeQuickTool, setActiveQuickTool] = useState<"cutout" | "detail" | "watermark" | "image-edit" | "translate" | "hot" | null>(null);
const [smartCutoutImage, setSmartCutoutImage] = useState<SmartCutoutImageItem | null>(null);
const [smartCutoutBatchImages, setSmartCutoutBatchImages] = useState<SmartCutoutImageItem[]>([]);
const [smartCutoutBackgroundColor, setSmartCutoutBackgroundColor] = useState("#ffffff");
@@ -1455,6 +1569,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const [imageWorkbenchProgress, setImageWorkbenchProgress] = useState(0);
const [isProductUploadDragging, setIsProductUploadDragging] = useState(false);
const [cloneOutput, setCloneOutput] = useState<CloneOutputKey>(defaultCloneOutput);
const [isCloneTemplateStripVisible, setIsCloneTemplateStripVisible] = useState(false);
const [videoHistoryVisible, setVideoHistoryVisible] = useState(false);
const [isVideoWorkspaceVisible, setIsVideoWorkspaceVisible] = useState(false);
const [videoPlanTrigger, setVideoPlanTrigger] = useState(0);
@@ -1905,24 +2020,25 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const [detailStatus, setDetailStatus] = useState<DetailStatus>("idle");
const [detailResultUrl, setDetailResultUrl] = useState<string | null>(null);
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 [hotPlatform, setHotPlatform] = useState(platformOptions[0]);
const [hotMarket, setHotMarket] = useState(marketOptions[0]);
const [hotLanguage, setHotLanguage] = useState(getPlatformDefaultLanguage(platformOptions[0], marketOptions[0]));
const [hotRatio, setHotRatio] = useState(getQuickSetRatioValue(getPlatformDefaultRatio(platformOptions[0], "detail")));
const [hotStatus, setHotStatus] = useState<DetailStatus>("idle");
const [hotResultUrl, setHotResultUrl] = useState<string | null>(null);
const [hotProgress, setHotProgress] = useState(0);
const productSetRatioOptions = useMemo(
() => getPlatformRatioOptions(productSetPlatform, productSetOutput),
[productSetOutput, productSetPlatform],
);
const hotUploadedRatioOption = useMemo(
() => cloneOutput === "hot" ? formatUploadedImageRatio(cloneReferenceImages[0]) : null,
[cloneOutput, cloneReferenceImages],
);
const baseCloneRatioOptions = useMemo(
() => getPlatformRatioOptions(platform, cloneOutput),
[cloneOutput, platform],
);
const cloneRatioOptions = useMemo(
() => hotUploadedRatioOption
? getUniqueRatioOptions([...baseCloneRatioOptions, hotUploadedRatioOption])
: baseCloneRatioOptions,
[baseCloneRatioOptions, hotUploadedRatioOption],
);
const cloneRatioOptions = baseCloneRatioOptions;
const productSetLanguageOptions = useMemo(
() => getPlatformLanguageOptions(productSetPlatform, productSetMarket),
[productSetMarket, productSetPlatform],
@@ -1935,6 +2051,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
() => getPlatformLanguageOptions(detailPlatform, detailMarket),
[detailMarket, detailPlatform],
);
const hotLanguageOptions = useMemo(
() => getPlatformLanguageOptions(hotPlatform, hotMarket),
[hotMarket, hotPlatform],
);
const ecommerceMentionImages: MentionImageOption[] = [
...productImages.map((image, index) => ({ ...image, label: `商品图 ${index + 1}` })),
...cloneReferenceImages.map((image, index) => ({ ...image, label: `参考图 ${index + 1}` })),
@@ -1952,6 +2072,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const selectedProductSetOutput =
productSetOutputOptions.find((option) => option.key === productSetOutput) ?? productSetOutputOptions[0]!;
const selectedCloneOutput = cloneOutputOptions.find((option) => option.key === cloneOutput) ?? cloneOutputOptions[1]!;
const activeCloneTemplateCards = cloneTemplateCards[cloneOutput === "hot" ? "set" : cloneOutput];
const cloneRequirementPlaceholder =
cloneOutput === "model"
? "建议包含以下信息:产品名称、核心卖点、期望场景、模特外貌描述(如小麦色皮肤、齐刘海、眼角有泪痣)、具体参数"
@@ -1965,6 +2086,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const canGenerate = productImages.length > 0 && status !== "generating";
const canGenerateTryOn = garmentImages.length > 0 && tryOnStatus !== "generating" && tryOnStatus !== "modeling";
const canGenerateDetail = detailProductImages.length > 0 && detailStatus !== "generating";
const canGenerateHot = cloneReferenceImages.length > 0 && hotStatus !== "generating";
const cloneVideoDurationProgress =
((cloneVideoDuration - cloneVideoDurationMin) / (cloneVideoDurationMax - cloneVideoDurationMin)) * 100;
const cloneVideoDurationStyle: CSSProperties = useMemo(
@@ -2079,21 +2201,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const openSmartCutoutUpload = () => {
clearSmartCutoutTransition();
setSmartCutoutTransitionMessage({
title: "正在进入智能抠图",
subtitle: "为你打开图片处理工具",
});
setActiveQuickTool("cutout");
setSmartCutoutBatchImages((current) => {
revokeSmartCutoutItems(current);
return [];
});
setSmartCutoutImage((current) => {
revokeSmartCutoutItem(current);
return null;
});
setIsSmartCutoutComparing(false);
setComposerMenu(null);
toast.info("功能正在优化中");
};
const openWatermarkRemovalPage = () => {
@@ -2307,9 +2416,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const openImageTranslatePage = () => {
clearSmartCutoutTransition();
setActiveQuickTool("translate");
setComposerMenu(null);
setIsCloneSettingsCollapsed(false);
toast.info("功能正在优化中");
};
const closeImageTranslatePage = () => {
@@ -2843,6 +2951,16 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
if (!selectedDetailModules.length) setSelectedDetailModules(defaultCloneDetailModuleIds);
};
const openHotClonePage = () => {
clearSmartCutoutTransition();
setActiveQuickTool("hot");
setComposerMenu(null);
setIsCloneSettingsCollapsed(false);
setIsQuickPanelCollapsed(false);
setPreviewZoom(1);
resetQuickSetSelectState();
};
const closeSmartCutoutTool = () => {
runSmartCutoutPageTransition(
{
@@ -3201,6 +3319,17 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
}
};
const removeCloneReferenceImage = (imageId: string) => {
setCloneReferenceImages((current) => {
const next = current.filter((item) => item.id !== imageId);
if (next.length === 0) {
setHotStatus("idle");
setHotResultUrl(null);
}
return next;
});
};
const handleCloneReferenceUpload = (event: ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (!files?.length) return;
@@ -3305,23 +3434,29 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const normalizedPlatform = normalizePlatform(nextPlatform);
setPlatform(normalizedPlatform);
setRatio((current) =>
cloneOutput === "hot" && current.startsWith("上传图片") && hotUploadedRatioOption
? hotUploadedRatioOption
: normalizeRatioForPlatform(normalizedPlatform, current, cloneOutput),
normalizeRatioForPlatform(normalizedPlatform, current, cloneOutput),
);
setLanguage(getPlatformDefaultLanguage(normalizedPlatform, market));
};
const handleCloneOutputChange = (nextOutput: CloneOutputKey) => {
setCloneOutput(nextOutput);
setIsCloneTemplateStripVisible(true);
if (nextOutput !== "video") setIsVideoWorkspaceVisible(false);
setRatio((current) =>
nextOutput === "hot" && current.startsWith("上传图片") && hotUploadedRatioOption
? hotUploadedRatioOption
: normalizeRatioForPlatform(platform, current, nextOutput),
normalizeRatioForPlatform(platform, current, nextOutput),
);
};
const handleCloneModeTabClick = (nextOutput: CloneOutputKey) => {
if (nextOutput === cloneOutput) {
setIsCloneTemplateStripVisible((visible) => !visible);
return;
}
handleCloneOutputChange(nextOutput);
setComposerMenu(null);
};
const handleCloneMarketChange = (nextMarket: string) => {
const normalizedMarket = normalizeMarket(nextMarket);
setMarket(normalizedMarket);
@@ -3457,14 +3592,12 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
useEffect(() => {
setRatio((current) => {
const platformRatios = getPlatformRatioOptions(platform, cloneOutput);
const availableRatios = hotUploadedRatioOption ? getUniqueRatioOptions([...platformRatios, hotUploadedRatioOption]) : platformRatios;
if (current.startsWith("上传图片") && hotUploadedRatioOption) return hotUploadedRatioOption;
if (availableRatios.includes(current)) return current;
if (platformRatios.includes(current)) return current;
const normalizedRatio = normalizeRatioToken(current);
const matchedRatio = availableRatios.find((option) => normalizeRatioToken(option).includes(normalizedRatio));
const matchedRatio = platformRatios.find((option) => normalizeRatioToken(option).includes(normalizedRatio));
return matchedRatio ?? getPlatformDefaultRatio(platform, cloneOutput);
});
}, [cloneOutput, hotUploadedRatioOption, platform]);
}, [cloneOutput, platform]);
useEffect(() => {
if (skipInitialCloneAutoSaveRef.current) {
@@ -4196,6 +4329,133 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
);
};
const handleHotPlatformChange = (nextPlatform: string) => {
const normalizedPlatform = normalizePlatform(nextPlatform);
setHotPlatform(normalizedPlatform);
setHotLanguage(getPlatformDefaultLanguage(normalizedPlatform, hotMarket));
setHotRatio((current) => getQuickSetRatioValue(current));
};
const handleHotMarketChange = (nextMarket: string) => {
const normalizedMarket = normalizeMarket(nextMarket);
setHotMarket(normalizedMarket);
setHotLanguage(getPlatformDefaultLanguage(hotPlatform, normalizedMarket));
};
const handleHotAiWrite = () => {
setHotRequirement(
"1.产品名称:便携式咖啡保温杯\n2.核心卖点:316不锈钢内胆、12小时长效保温、防漏便携、大容量\n3.参考风格:极简日系、暖光氛围、生活场景\n4.期望场景:办公桌面、户外通勤、运动健身\n5.具体参数:容量500ml、口径4.5cm、高度22cm",
);
};
const stopHotProgress = () => {
if (hotProgressRef.current !== null) {
window.clearInterval(hotProgressRef.current);
hotProgressRef.current = null;
}
};
const startHotProgress = () => {
stopHotProgress();
setHotProgress(0);
hotProgressRef.current = window.setInterval(() => {
setHotProgress((prev) => {
if (prev >= 90) {
stopHotProgress();
return 90;
}
return prev + (90 - prev) * 0.06;
});
}, 500);
};
const handleHotGenerate = () => {
if (!canGenerateHot) return;
imageAbortRef.current = { current: false };
lastFailedActionRef.current = null;
startHotProgress();
void generateEcommerceImage(
"hot", cloneReferenceImages, hotRequirement,
hotPlatform, hotRatio, hotLanguage, hotMarket,
undefined,
(s: string) => {
setHotStatus(s as DetailStatus);
if (s === "done") {
stopHotProgress();
setHotProgress(100);
} else if (s === "failed" || s === "idle") {
stopHotProgress();
setHotProgress(0);
}
},
(res) => setHotResultUrl(res[0]?.src ?? null),
);
};
const handleHotMaterialMouseEnter = (src: string, event: ReactMouseEvent<HTMLElement>) => {
const rect = event.currentTarget.getBoundingClientRect();
const previewHalfWidth = 150;
const previewHeight = 360;
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 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" });
};
const handleHotMaterialMouseLeave = () => setHotMaterialHoverZoom(null);
const renderHotMaterialThumbs = (items: CloneImageItem[], onRemove: (imageId: string) => void) => (
<div className="ecom-quick-upload-thumbs" aria-label="已上传商品素材">
{items.map((item) => (
<figure
key={item.id}
className="ecom-command-asset-thumb ecom-quick-upload-thumb"
onMouseEnter={(e) => handleHotMaterialMouseEnter(item.src, e)}
onMouseLeave={handleHotMaterialMouseLeave}
>
<img src={item.src} alt={item.name} />
<button
type="button"
className="ecom-hot-material-delete"
aria-label={`删除${item.name || "图片"}`}
onClick={(event) => {
event.stopPropagation();
setHotMaterialHoverZoom(null);
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>
))}
</div>
);
const closeHotClonePage = () => {
stopHotProgress();
setActiveQuickTool(null);
setHotStatus("idle");
setHotResultUrl(null);
setHotProgress(0);
setHotRequirement("");
setIsHotMaterialDragging(false);
setHotMaterialHoverZoom(null);
setComposerMenu(null);
};
const resetTask = () => {
setSetImages([]);
setProductSetRequirement("");
@@ -4258,6 +4518,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const isWatermarkTool = isCloneTool && activeQuickTool === "watermark";
const isImageEditTool = isCloneTool && activeQuickTool === "image-edit";
const isTranslateTool = isCloneTool && activeQuickTool === "translate";
const isHotCloneTool = isCloneTool && activeQuickTool === "hot";
const pageLabel = isSetTool ? "商品套图" : isDetail ? "A+/详情页" : isTryOn ? "AI服饰穿戴" : activeToolMeta?.label || "商品工具";
const setPrimaryLabel =
setImages.length === 0
@@ -4535,6 +4796,19 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
{ key: "ratio", label: "尺寸/比例", value: getQuickSetRatioValue(detailRatio), options: quickSetRatioOptions, onChange: setDetailRatio },
];
const quickHotBasicSelects: Array<{
key: CloneBasicSelectKey;
label: string;
value: string;
options: string[];
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: "ratio", label: "尺寸/比例", value: getQuickSetRatioValue(hotRatio), options: quickSetRatioOptions, onChange: setHotRatio },
];
const cloneModelSelects: Array<{
key: CloneModelSelectKey;
label: string;
@@ -4814,8 +5088,6 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
? cloneModelPanelTab === "scene" ? "场景设置" : "模特设置"
: cloneOutput === "video"
? String(cloneVideoDuration) + "秒 " + (cloneVideoQuality === "standard" ? "720P" : "1080P")
: cloneOutput === "hot"
? cloneReplicateLevel === "style" ? "风格复刻" : "高度复刻"
: "换装素材";
const renderComposerMenu = () => {
@@ -4952,48 +5224,6 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
<input type="range" min={cloneVideoDurationMin} max={cloneVideoDurationMax} step={5} value={cloneVideoDuration} onChange={(event) => setCloneVideoDuration(clampCloneVideoDuration(Number(event.target.value)))} />
</label>
</>
) : cloneOutput === "hot" ? (
<>
<header><strong></strong><span>{cloneReferenceImages.length}/{maxCloneReferenceImages}</span></header>
<div className="ecom-command-hot-layout">
<button
type="button"
className={`ecom-command-hot-upload${isCloneReferenceDragging ? " is-dragging" : ""}${cloneReferenceImages.length ? " has-image" : ""}`}
onClick={() => cloneReferenceInputRef.current?.click()}
onDragOver={handleCloneReferenceDragOver}
onDragLeave={handleCloneReferenceDragLeave}
onDrop={handleCloneReferenceDrop}
>
{cloneReferenceImages[0]?.src ? (
<>
<span className="ecom-command-hot-thumb-grid">
{cloneReferenceImages.map((image, index) => (
<span key={image.id} className="ecom-command-hot-thumb">
<img src={image.src} alt={`参考图 ${index + 1}`} />
<span className="ecom-command-hot-zoom" aria-hidden="true">
<img src={image.src} alt="" />
</span>
</span>
))}
</span>
<span> {cloneReferenceImages.length} </span>
</>
) : (
<>
<strong>+ </strong>
<span></span>
</>
)}
</button>
<div className="ecom-command-hot-levels">
{cloneReplicateLevelOptions.map((option) => (
<button key={option.key} type="button" className={cloneReplicateLevel === option.key ? "is-active" : ""} onClick={() => setCloneReplicateLevel(option.key)}>
<strong>{option.title}</strong><span>{option.desc}</span>
</button>
))}
</div>
</div>
</>
) : (
<>
<header><strong></strong><span></span></header>
@@ -5065,6 +5295,60 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
toast.success("提示词已填入指令栏");
};
const applyComposerPrompt = (prompt: string) => {
const nextValue = prompt.slice(0, 500);
setActiveQuickTool(null);
setComposerMenu(null);
setRequirement(nextValue);
syncRequirementMentionQuery(nextValue, nextValue.length);
setInspirationPreview(null);
requestAnimationFrame(() => {
const textarea = requirementTextareaRef.current;
if (textarea) {
textarea.focus();
textarea.setSelectionRange(nextValue.length, nextValue.length);
textarea.scrollIntoView({ behavior: "smooth", block: "center" });
}
});
};
const addTemplateImageToComposer = async (card: CloneTemplateAsset) => {
if (productImages.length >= maxCloneProductImages) {
toast.info("模板图片已达上限");
return;
}
try {
const stamp = Date.now();
const uploaded = await aiGenerationClient.uploadAssetByUrl({
sourceUrl: card.mediaUrl,
name: `${card.id}-${stamp}`,
scope: ecommerceOssScopes.productSource,
});
const nextImage: CloneImageItem = {
id: `template-${card.id}-${stamp}`,
src: uploaded.url || card.mediaUrl,
name: card.title,
ossKey: uploaded.ossKey,
};
setProductImages((current) => [...current, nextImage].slice(0, maxCloneProductImages));
void readImageDimensions(nextImage.src)
.then(({ width, height }) => {
setProductImages((current) =>
current.map((item) => (item.id === nextImage.id ? { ...item, width, height } : item)),
);
})
.catch(() => undefined);
} catch {
toast.error("模板图片导入失败");
}
};
const handleCloneTemplateCardClick = (card: CloneTemplateAsset) => {
void addTemplateImageToComposer(card);
applyComposerPrompt(card.prompt);
};
const inspirationPreviewOverlay =
inspirationPreview && typeof document !== "undefined"
? createPortal(
@@ -5403,7 +5687,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
key={option.key}
type="button"
className={cloneOutput === option.key ? "is-active" : ""}
onClick={() => handleCloneOutputChange(option.key)}
onClick={() => handleCloneModeTabClick(option.key)}
>
<span className={`ecom-command-mode-icon ecom-command-mode-icon--${option.key}`} aria-hidden="true">{option.icon}</span>
<strong>{option.label}</strong>
@@ -5502,10 +5786,30 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
</div>
{renderComposerMenu()}
</div>
{(status === "idle" || status === "ready") && !showMainVideoWorkspace && isCloneTemplateStripVisible ? (
<section className={`ecom-command-template-strip ecom-command-template-strip--${cloneOutput}`} aria-label="模板卡片">
{activeCloneTemplateCards.map((card) => (
<button
key={card.id}
type="button"
className="ecom-command-template-card"
aria-label={card.title}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
handleCloneTemplateCardClick(card);
}}
>
<span className="ecom-command-template-card__blank" aria-hidden="true" />
</button>
))}
</section>
) : null}
{(status === "idle" || status === "ready") && !showMainVideoWorkspace ? (
<section className="ecom-command-quick-board" aria-label="快捷功能">
{[
{ label: "A+/详情页", tone: "detail", icon: <LayoutOutlined />, onClick: openQuickDetailPage },
{ label: "爆款复刻", tone: "hot", icon: <FireOutlined />, onClick: openHotClonePage },
{ label: "图片修改", tone: "edit", icon: <EditOutlined />, onClick: openImageWorkbenchPage },
{ label: "智能抠图", tone: "cutout", icon: <ScissorOutlined />, onClick: openSmartCutoutUpload },
{ label: "去除水印", tone: "watermark", icon: <ClearOutlined />, onClick: openWatermarkRemovalPage },
@@ -6523,6 +6827,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
);
const quickDetailVisibleSelect = quickDetailBasicSelects.find((item) => item.key === visibleQuickSetSelect) ?? null;
const quickHotVisibleSelect = quickHotBasicSelects.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+详情页生成">
@@ -6720,6 +7025,270 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
</main>
);
const hotClonePreview = (
<main key="quick-hot" 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={closeHotClonePage}>
</button>
<button type="button" className="ecom-quick-set-back" onClick={closeHotClonePage}>
</button>
</header>
<section>
<strong><FileImageOutlined /> </strong>
{productImages.length ? (
<div
role="button"
tabIndex={0}
className={`ecom-quick-set-upload ecom-quick-hot-material has-images${isHotMaterialDragging ? " is-dragging" : ""}`}
onClick={() => hotMaterialInputRef.current?.click()}
onKeyDown={(event) => openQuickUploadWithKeyboard(event, hotMaterialInputRef)}
onDragOver={(event) => { event.preventDefault(); event.stopPropagation(); if (event.dataTransfer.types.includes("Files")) setIsHotMaterialDragging(true); }}
onDragLeave={(event) => { event.preventDefault(); event.stopPropagation(); if (event.currentTarget === event.target || !event.currentTarget.contains(event.relatedTarget as Node)) setIsHotMaterialDragging(false); }}
onDrop={(event) => { event.preventDefault(); event.stopPropagation(); setIsHotMaterialDragging(false); const files = Array.from(event.dataTransfer.files); if (files.length) addProductImages(files); }}
>
{renderHotMaterialThumbs(productImages, removeProductImage)}
<button
type="button"
className="ecom-quick-hot-add-btn"
aria-label="添加更多素材"
onClick={(event) => {
event.stopPropagation();
hotMaterialInputRef.current?.click();
}}
>
<PlusOutlined />
</button>
</div>
) : (
<div
role="button"
tabIndex={0}
className={`ecom-quick-set-upload ecom-quick-hot-material${isHotMaterialDragging ? " is-dragging" : ""}`}
onClick={() => hotMaterialInputRef.current?.click()}
onKeyDown={(event) => openQuickUploadWithKeyboard(event, hotMaterialInputRef)}
onDragOver={(event) => { event.preventDefault(); event.stopPropagation(); if (event.dataTransfer.types.includes("Files")) setIsHotMaterialDragging(true); }}
onDragLeave={(event) => { event.preventDefault(); event.stopPropagation(); if (event.currentTarget === event.target || !event.currentTarget.contains(event.relatedTarget as Node)) setIsHotMaterialDragging(false); }}
onDrop={(event) => { event.preventDefault(); event.stopPropagation(); setIsHotMaterialDragging(false); const files = Array.from(event.dataTransfer.files); if (files.length) addProductImages(files); }}
>
<FileImageOutlined />
<span></span>
<em> {maxCloneProductImages} </em>
<b>+ </b>
</div>
)}
<input
ref={hotMaterialInputRef}
type="file"
accept="image/*"
multiple
className="ecom-command-hidden-file"
onChange={handleProductUpload}
aria-label="上传爆款复刻素材"
/>
</section>
<section>
<strong><FireOutlined /> </strong>
<div
role="button"
tabIndex={0}
className={`ecom-quick-set-upload ecom-quick-hot-material ecom-quick-hot-reference${cloneReferenceImages.length ? " has-images" : ""}${isCloneReferenceDragging ? " is-dragging" : ""}`}
onClick={() => cloneReferenceInputRef.current?.click()}
onKeyDown={(event) => openQuickUploadWithKeyboard(event, cloneReferenceInputRef)}
onDragOver={(event) => { event.preventDefault(); event.stopPropagation(); if (event.dataTransfer.types.includes("Files")) setIsCloneReferenceDragging(true); }}
onDragLeave={(event) => { event.preventDefault(); event.stopPropagation(); if (event.currentTarget === event.target || !event.currentTarget.contains(event.relatedTarget as Node)) setIsCloneReferenceDragging(false); }}
onDrop={(event) => { event.preventDefault(); event.stopPropagation(); setIsCloneReferenceDragging(false); const files = Array.from(event.dataTransfer.files); if (files.length) addCloneReferenceImages(files); }}
>
<FileImageOutlined />
<span></span>
<em> {maxCloneReferenceImages} </em>
<b>+ </b>
{cloneReferenceImages.length ? (
<>
{renderHotMaterialThumbs(cloneReferenceImages, removeCloneReferenceImage)}
<button
type="button"
className="ecom-quick-hot-add-btn"
aria-label="添加更多参考图"
onClick={(event) => {
event.stopPropagation();
cloneReferenceInputRef.current?.click();
}}
>
<PlusOutlined />
</button>
</>
) : null}
</div>
<input
ref={cloneReferenceInputRef}
type="file"
accept="image/*"
multiple
className="ecom-command-hidden-file"
onChange={handleCloneReferenceUpload}
aria-label="上传爆款复刻参考图"
/>
</section>
<section>
<strong></strong>
<div className="ecom-quick-detail-modules">
{cloneReplicateLevelOptions.map((option) => (
<button
key={option.key}
type="button"
className={cloneReplicateLevel === option.key ? "is-active" : ""}
onClick={() => setCloneReplicateLevel(option.key)}
>
<strong>{option.title}</strong>
<span>{option.desc}</span>
</button>
))}
</div>
</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">
{quickHotBasicSelects.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>
{quickHotVisibleSelect ? (
<div
className={`ecom-quick-set-dropdown ecom-quick-set-dropdown--${quickHotVisibleSelect.key}${isQuickSetSelectClosing ? " is-closing" : ""}`}
role="listbox"
aria-label={quickHotVisibleSelect.label}
>
{quickHotVisibleSelect.options.map((option) => (
<button
key={option}
type="button"
className={quickHotVisibleSelect.value === option ? "is-active" : ""}
onClick={() => {
quickHotVisibleSelect.onChange(option);
closeQuickSetSelect();
}}
>
{formatRatioDisplayValue(option)}
</button>
))}
</div>
) : null}
</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={handleHotAiWrite}>AI </button>
</div>
<div className="ecom-quick-hot-requirement__input">
<textarea
value={hotRequirement}
onChange={(event) => setHotRequirement(event.target.value.slice(0, 500))}
placeholder="建议包含以下信息:产品名称、核心卖点、参考风格、期望场景、具体参数"
maxLength={500}
/>
<span>{hotRequirement.length}/500</span>
</div>
</section>
<div className="ecom-quick-hot-actions">
<button type="button" className="ecom-quick-set-primary ecom-quick-hot-generate" onClick={handleHotGenerate} disabled={!canGenerateHot}>
{hotStatus === "generating" ? <LoadingOutlined /> : "✦"}
</button>
<button
type="button"
className={`ecom-quick-set-primary ecom-quick-set-primary--cancel${hotStatus !== "generating" ? " is-disabled" : ""}`}
onClick={hotStatus === "generating" ? handleCancelGenerate : undefined}
disabled={hotStatus !== "generating"}
>
</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>
<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}>
{hotStatus === "done" && hotResultUrl ? (
<section className="ecom-quick-detail-result" style={{ transform: `scale(${previewZoom})` }}>
<img src={hotResultUrl} alt="爆款复刻结果" />
<button
type="button"
className="ecom-quick-detail-download"
onClick={() => {
const link = document.createElement("a");
link.href = hotResultUrl;
link.download = `爆款复刻-${Date.now()}.png`;
document.body.appendChild(link);
link.click();
link.remove();
}}
>
<CloudUploadOutlined />
</button>
</section>
) : hotStatus === "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(hotProgress)}%` }} />
</div>
<em className="ecom-quick-set-progress-text">{Math.round(hotProgress)}%</em>
</section>
) : hotStatus === "failed" ? (
<section className="ecom-quick-set-failed">
<FrownOutlined />
<strong></strong>
<span></span>
<button type="button" onClick={handleHotGenerate} disabled={!canGenerateHot}></button>
</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">
@@ -6835,6 +7404,12 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
<div key={`quick-${activeQuickTool}`} className="ecom-quick-page-wrap ecom-tool-page-enter">
{quickDetailPreview}
</div>
)
: isHotCloneTool
? (
<div key={`quick-${activeQuickTool}`} className="ecom-quick-page-wrap ecom-tool-page-enter">
{hotClonePreview}
</div>
)
: clonePreview
: placeholderPreview;
@@ -6865,7 +7440,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" : ""}`}
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" : ""}`}
data-tool={activeTool}
aria-label={pageLabel}
>
@@ -6889,7 +7464,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
{isSetTool ? setPanel : isDetail ? detailPanel : isTryOn ? tryOnPanel : isCloneTool ? clonePanel : placeholderPanel}
</aside>
{isCloneTool && !isSmartCutoutTool && !isQuickDetailTool && !isWatermarkTool && !isTranslateTool && !isImageEditTool ? (
{isCloneTool && !isSmartCutoutTool && !isQuickDetailTool && !isWatermarkTool && !isTranslateTool && !isImageEditTool && !isHotCloneTool ? (
<button
type="button"
className="clone-ai-settings-toggle"
@@ -0,0 +1 @@
export * from "./ecommerceGenerationPersistence.ts";
@@ -0,0 +1 @@
export * from "./ecommerceImageValidation.ts";
@@ -0,0 +1 @@
export * from "./ecommerceTemplates.ts";
@@ -381,108 +381,6 @@ export default function EcommerceClonePanel({
</section>
) : null}
{cloneOutput === "hot" ? (
<section className="clone-ai-replicate-panel" aria-label="爆款图复刻设置">
<div className="clone-ai-dynamic-head">
<strong></strong>
<span></span>
</div>
<div className="clone-ai-replicate-section">
<span className="clone-ai-replicate-title"></span>
<div className="clone-ai-replicate-tabs" role="tablist" aria-label="参考内容来源">
<button
type="button"
className={cloneReferenceMode === "upload" ? "is-active" : ""}
aria-selected={cloneReferenceMode === "upload"}
onClick={() => setCloneReferenceMode("upload")}
>
</button>
<button
type="button"
className={cloneReferenceMode === "link" ? "is-active" : ""}
aria-selected={cloneReferenceMode === "link"}
onClick={() => setCloneReferenceMode("link")}
>
</button>
</div>
{cloneReferenceMode === "upload" ? (
<button
type="button"
className={`clone-ai-replicate-upload${isCloneReferenceDragging ? " is-dragging" : ""}${cloneReferenceImages.length ? " has-files" : ""}`}
onClick={() => cloneReferenceInputRef.current?.click()}
onDragOver={handleCloneReferenceDragOver}
onDragLeave={handleCloneReferenceDragLeave}
onDrop={handleCloneReferenceDrop}
>
{cloneReferenceImages.length ? (
<>
<div className="clone-ai-replicate-files">
{cloneReferenceImages.map((item) => (
<figure
key={item.id}
className="clone-ai-replicate-file"
onMouseEnter={(e) => handleFileMouseEnter(item.src, e)}
onMouseLeave={handleFileMouseLeave}
>
<img src={item.src} alt="" />
</figure>
))}
</div>
<span className="clone-ai-replicate-add-more">
<CloudUploadOutlined />
</span>
</>
) : (
<span>
<CloudUploadOutlined />
<span className="clone-ai-replicate-upload-text"></span>
</span>
)}
<em>{cloneReferenceImages.length ? `已选 ${cloneReferenceImages.length}/${maxCloneReferenceImages}` : `最多 ${maxCloneReferenceImages}`}</em>
{isCloneReferenceDragging ? (
<div className="clone-ai-replicate-upload-overlay">
<CloudUploadOutlined />
<span></span>
</div>
) : null}
</button>
) : (
<label className="clone-ai-replicate-link">
<input placeholder="粘贴商品图或详情页链接" />
</label>
)}
<input
ref={cloneReferenceInputRef}
type="file"
accept="image/jpeg,image/png,image/webp"
multiple
onChange={handleCloneReferenceUpload}
aria-label="上传参考图片"
/>
</div>
<div className="clone-ai-replicate-section">
<span className="clone-ai-replicate-title"></span>
<div className="clone-ai-replicate-levels" role="toolbar" aria-label="复刻程度">
{cloneReplicateLevelOptions.map((option) => (
<button
key={option.key}
type="button"
className={cloneReplicateLevel === option.key ? "is-active" : ""}
aria-pressed={cloneReplicateLevel === option.key}
onClick={() => setCloneReplicateLevel(option.key)}
>
<strong>{option.title}</strong>
<span>{option.desc}</span>
</button>
))}
</div>
</div>
</section>
) : null}
{cloneOutput === "set" ? (
<section className="clone-ai-count-panel" aria-label="套图图片数量">
<div className="clone-ai-dynamic-head">
@@ -0,0 +1 @@
export * from "./workbenchDownload.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./useGenerationTasks.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./useTypewriter.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./backgroundTaskRunner.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./index.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./useAppStore.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./useGenerationStore.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./useProjectStore.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./useSessionStore.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./useTaskStore.ts";
+612 -9
View File
@@ -4537,6 +4537,275 @@
display: none !important;
}
.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page {
display: block !important;
height: 100% !important;
min-height: calc(100vh - 58px) !important;
overflow: hidden !important;
background: #f3f5f8 !important;
}
.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .product-clone-shell {
display: block !important;
width: 100% !important;
height: 100% !important;
padding: 0 !important;
}
.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .product-clone-rail,
.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .product-clone-panel,
.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .clone-ai-settings-toggle,
.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-command-history {
display: none !important;
}
/* ── Hot Clone: requirement input in left panel ── */
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-requirement {
display: flex !important;
flex-direction: column !important;
gap: 8px !important;
margin-top: 2px !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-requirement__head {
display: flex !important;
align-items: center !important;
justify-content: space-between !important;
gap: 8px !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-requirement__head > strong {
font-size: 13px !important;
font-weight: 800 !important;
color: #1a2b3c !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-requirement__input {
position: relative !important;
min-height: 158px !important;
border: 1px dashed rgba(30, 189, 219, 0.34) !important;
border-radius: 8px !important;
background: linear-gradient(180deg, rgba(237, 248, 255, 0.72), rgba(255, 255, 255, 0.94)) !important;
overflow: hidden !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-requirement__input textarea {
display: block !important;
width: 100% !important;
min-height: 140px !important;
max-height: 240px !important;
resize: none !important;
border: 0 !important;
outline: none !important;
padding: 14px 14px 24px !important;
color: #172636 !important;
background: transparent !important;
font-size: 13px !important;
font-weight: 700 !important;
line-height: 1.6 !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-requirement__input textarea::placeholder {
color: #9badb9 !important;
font-weight: 500 !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-requirement__input > span {
position: absolute !important;
right: 12px !important;
bottom: 6px !important;
color: #9badb9 !important;
font-size: 11px !important;
font-weight: 600 !important;
pointer-events: none !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-requirement__ai {
padding: 3px 12px !important;
border: 1.5px solid rgba(16, 115, 204, 0.18) !important;
border-radius: 20px !important;
background: linear-gradient(135deg, rgba(16, 115, 204, 0.06), rgba(25, 173, 200, 0.06)) !important;
color: #1073cc !important;
font-size: 11px !important;
font-weight: 800 !important;
cursor: pointer !important;
white-space: nowrap !important;
flex-shrink: 0 !important;
transition: background 160ms ease, border-color 160ms ease !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-requirement__ai:hover {
background: linear-gradient(135deg, rgba(16, 115, 204, 0.12), rgba(25, 173, 200, 0.12)) !important;
border-color: rgba(16, 115, 204, 0.3) !important;
}
/* ── Hot Clone: material upload with images ── */
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-material.has-images {
display: flex !important;
flex-wrap: wrap !important;
align-items: flex-start !important;
justify-content: flex-start !important;
gap: 10px !important;
padding: 12px !important;
place-items: unset !important;
background: #f9fafa !important;
border: 1px solid rgba(30, 189, 219, 0.22) !important;
border-style: solid !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-material.has-images .ecom-quick-upload-thumbs {
display: contents !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-material.has-images .ecom-quick-upload-thumbs figure {
position: relative !important;
width: 80px !important;
height: 80px !important;
margin: 0 !important;
border: 1px solid #e8edf0 !important;
border-radius: 10px !important;
overflow: hidden !important;
background: #fff !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-material.has-images .ecom-quick-upload-thumbs figure > img {
display: block !important;
width: 100% !important;
height: 100% !important;
object-fit: cover !important;
border-radius: 9px !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-material.has-images .ecom-quick-upload-thumbs figure > button {
position: absolute !important;
top: 4px !important;
right: 4px !important;
width: 20px !important;
height: 20px !important;
border: none !important;
border-radius: 50% !important;
background: rgba(0, 0, 0, 0.48) !important;
color: #fff !important;
font-size: 11px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
cursor: pointer !important;
opacity: 0 !important;
transition: opacity 140ms ease, background 140ms ease !important;
padding: 0 !important;
z-index: 3 !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-material.has-images .ecom-quick-upload-thumbs figure:hover > button {
opacity: 1 !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-material.has-images .ecom-quick-upload-thumbs figure > button:hover {
background: rgba(220, 53, 69, 0.85) !important;
}
/* Hide old CSS zoom in material section (portal replaces it) */
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-material.has-images .ecom-command-asset-zoom {
display: none !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-add-btn {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
width: 80px !important;
height: 80px !important;
border: 1px solid #e8edf0 !important;
border-radius: 10px !important;
color: #3a4555 !important;
background: #f5f5f5 !important;
font-size: 22px !important;
cursor: pointer !important;
transition: background 160ms ease, border-color 160ms ease, color 160ms ease !important;
flex-shrink: 0 !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-add-btn:hover {
color: #ffffff !important;
background: linear-gradient(135deg, #1073cc, #1ebddb) !important;
border-color: #1073cc !important;
}
/* ── Hot Clone: sticky bottom action buttons ── */
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-actions {
position: sticky !important;
bottom: 0 !important;
z-index: 5 !important;
display: flex !important;
flex-direction: column !important;
gap: 8px !important;
margin: 0 -14px -86px -14px !important;
padding: 14px 14px 16px !important;
background: linear-gradient(to top, #feffff 60%, rgba(254, 255, 255, 0.92) 80%, transparent) !important;
backdrop-filter: blur(6px) !important;
flex-shrink: 0 !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-actions .ecom-quick-set-primary {
position: static !important;
left: auto !important;
right: auto !important;
bottom: auto !important;
width: 100% !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-actions .ecom-quick-set-primary--cancel.is-disabled {
color: #c0ccd4 !important;
background: #f0f3f5 !important;
border-color: #e4e9ec !important;
cursor: not-allowed !important;
opacity: 0.55 !important;
}
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-hot-actions .ecom-quick-set-primary--cancel.is-disabled:hover {
color: #c0ccd4 !important;
background: #f0f3f5 !important;
border-color: #e4e9ec !important;
}
/* ── Hot Clone: stage fills space without prompt ── */
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-set-stage {
grid-template-rows: auto minmax(0, 1fr) !important;
}
/* ── Hot Clone: portal zoom preview (avoids overflow clipping) ── */
.ecom-hot-material-zoom-portal {
position: fixed !important;
z-index: 2147483647 !important;
width: min(280px, calc(100vw - 24px)) !important;
max-height: 340px !important;
border: 1px solid rgba(30, 189, 219, 0.2) !important;
border-radius: 14px !important;
background: #ffffff !important;
padding: 8px !important;
box-shadow: 0 22px 48px rgba(20, 80, 100, 0.22) !important;
pointer-events: none !important;
isolation: isolate !important;
}
.ecom-hot-material-zoom-portal.is-above {
transform: translate(-50%, -100%) !important;
}
.ecom-hot-material-zoom-portal.is-below {
transform: translate(-50%, 0) !important;
}
.ecom-hot-material-zoom-portal img {
display: block !important;
width: 100% !important;
height: auto !important;
max-height: 324px !important;
border-radius: 8px !important;
object-fit: contain !important;
}
.ecommerce-standalone .product-clone-page[data-tool="clone"].is-watermark-page {
display: block !important;
height: 100% !important;
@@ -6783,31 +7052,36 @@
background: rgba(16, 115, 204, 0.28) !important;
}
.ecommerce-standalone .ecom-quick-detail-page .ecom-quick-set-panel {
.ecommerce-standalone .ecom-quick-detail-page .ecom-quick-set-panel,
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-set-panel {
overflow-y: auto !important;
padding-bottom: 16px !important;
scrollbar-width: auto !important;
scrollbar-color: rgba(16, 115, 204, 0.56) rgba(16, 115, 204, 0.08) !important;
}
.ecommerce-standalone .ecom-quick-detail-page .ecom-quick-set-panel::-webkit-scrollbar {
.ecommerce-standalone .ecom-quick-detail-page .ecom-quick-set-panel::-webkit-scrollbar,
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-set-panel::-webkit-scrollbar {
display: block !important;
width: 14px !important;
height: 14px !important;
}
.ecommerce-standalone .ecom-quick-detail-page .ecom-quick-set-panel::-webkit-scrollbar-track {
.ecommerce-standalone .ecom-quick-detail-page .ecom-quick-set-panel::-webkit-scrollbar-track,
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-set-panel::-webkit-scrollbar-track {
border-radius: 999px !important;
background: rgba(16, 115, 204, 0.08) !important;
}
.ecommerce-standalone .ecom-quick-detail-page .ecom-quick-set-panel::-webkit-scrollbar-thumb {
.ecommerce-standalone .ecom-quick-detail-page .ecom-quick-set-panel::-webkit-scrollbar-thumb,
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-set-panel::-webkit-scrollbar-thumb {
border: 3px solid rgba(248, 249, 250, 0.95) !important;
border-radius: 999px !important;
background: rgba(16, 115, 204, 0.56) !important;
}
.ecommerce-standalone .ecom-quick-detail-page .ecom-quick-set-panel::-webkit-scrollbar-thumb:hover {
.ecommerce-standalone .ecom-quick-detail-page .ecom-quick-set-panel::-webkit-scrollbar-thumb:hover,
.ecommerce-standalone .ecom-quick-hot-page .ecom-quick-set-panel::-webkit-scrollbar-thumb:hover {
background: rgba(16, 115, 204, 0.72) !important;
}
@@ -8766,6 +9040,56 @@
z-index: 0 !important;
}
.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-template-strip {
display: grid !important;
grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
gap: 12px !important;
width: 100% !important;
margin: 14px 0 10px !important;
}
.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-template-card {
position: relative !important;
display: block !important;
width: 100% !important;
aspect-ratio: 1.46 / 1 !important;
min-height: 92px !important;
padding: 0 !important;
border: 1px solid rgba(30, 189, 219, 0.18) !important;
border-radius: 16px !important;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(246, 251, 253, 0.98)),
#ffffff !important;
box-shadow:
0 16px 34px rgba(16, 115, 204, 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.94) !important;
overflow: hidden !important;
cursor: pointer !important;
transition: transform 180ms ease, border-color 180ms ease, box-shadow 180ms ease, background 180ms ease !important;
}
.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-template-card:hover {
transform: translateY(-1px) !important;
border-color: rgba(30, 189, 219, 0.34) !important;
box-shadow:
0 20px 42px rgba(16, 115, 204, 0.11),
0 0 0 1px rgba(30, 189, 219, 0.12) inset !important;
}
.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-template-card:focus-visible {
outline: none !important;
border-color: rgba(30, 189, 219, 0.48) !important;
box-shadow:
0 0 0 3px rgba(30, 189, 219, 0.15),
0 20px 42px rgba(16, 115, 204, 0.11) !important;
}
.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-template-card__blank {
display: block !important;
width: 100% !important;
height: 100% !important;
}
.ecommerce-standalone .product-clone-page[data-tool="clone"] .clone-ai-preview {
background: #f8f9fa !important;
transition: padding-top 520ms cubic-bezier(0.16, 1, 0.3, 1),
@@ -8984,6 +9308,10 @@
min-height: 224px !important;
}
.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-template-strip {
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
}
.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-quick-board button:not(:nth-child(6n + 1))::before {
content: none !important;
}
@@ -11773,6 +12101,15 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d
--quick-text: #164e63;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-quick-board .ecom-command-quick-card--hot {
--quick-accent: #e8590c;
--quick-bg: #fff4e6;
--quick-text: #5c2d0e;
--quick-icon: #d9480f;
--quick-border: rgba(232, 89, 12, 0.12);
--quick-shadow: rgba(232, 89, 12, 0.1);
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-quick-board,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-quick-board button,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-quick-board button > span,
@@ -13172,6 +13509,7 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-quick-board .ecom-command-quick-card--detail,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-quick-board .ecom-command-quick-card--hot,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-quick-board .ecom-command-quick-card--edit,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-quick-board .ecom-command-quick-card--cutout,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-quick-board .ecom-command-quick-card--watermark {
@@ -13397,6 +13735,16 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d
--quick-shadow: rgba(122, 90, 248, 0.1) !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-quick-board .ecom-command-quick-card--hot {
--quick-accent: #e8590c !important;
--quick-bg: #fff4e6 !important;
--quick-text: #5c2d0e !important;
--quick-icon: #d9480f !important;
--quick-icon-bg: rgba(232, 89, 12, 0.13) !important;
--quick-border: rgba(232, 89, 12, 0.12) !important;
--quick-shadow: rgba(232, 89, 12, 0.1) !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-quick-board .ecom-command-quick-card--edit {
--quick-accent: #cc6b14 !important;
--quick-bg: #fff2e5 !important;
@@ -13492,7 +13840,7 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d
/* #/imageWorkbench quick actions: soften each action so the tones blend into the page. */
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-wrap.ecom-command-composer-wrap:has(.ecom-inspiration-lab) .ecom-command-quick-board {
background: rgba(255, 255, 255, 0.3) !important;
background: transparent !important;
box-shadow: none !important;
}
@@ -13600,7 +13948,7 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d
/* #/imageWorkbench composer redesign: mode tabs outside, settings and assets inside. */
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-mode-tabs {
display: grid !important;
grid-template-columns: repeat(5, minmax(0, 1fr)) !important;
grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
gap: 8px !important;
width: min(100%, 760px) !important;
margin: 0 auto 12px !important;
@@ -13887,7 +14235,7 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d
@media (max-width: 900px) {
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-mode-tabs {
grid-template-columns: repeat(5, minmax(94px, 1fr)) !important;
grid-template-columns: repeat(4, minmax(104px, 1fr)) !important;
width: 100% !important;
overflow-x: auto !important;
overscroll-behavior-x: contain !important;
@@ -13901,7 +14249,7 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d
@media (max-width: 640px) {
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-mode-tabs {
grid-template-columns: repeat(5, minmax(88px, 1fr)) !important;
grid-template-columns: repeat(4, minmax(92px, 1fr)) !important;
margin-bottom: 10px !important;
}
@@ -14082,6 +14430,261 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d
font-size: 22px !important;
}
/* Hot clone uploaded material thumbnails: compact grid and consistent delete control. */
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-set-upload.has-images {
display: flex !important;
flex-wrap: wrap !important;
align-items: flex-start !important;
justify-content: flex-start !important;
align-content: flex-start !important;
gap: 10px !important;
width: 100% !important;
min-height: 0 !important;
height: auto !important;
padding: 10px !important;
border: 1px solid #e8edf0 !important;
border-radius: 8px !important;
background: #ffffff !important;
box-shadow: none !important;
transform: none !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-set-upload.has-images:hover,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-set-upload.has-images:focus-visible,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-set-upload.has-images.is-dragging {
border-color: rgba(30, 189, 219, 0.42) !important;
background: #fbfdff !important;
box-shadow: 0 10px 24px rgba(16, 115, 204, 0.08) !important;
transform: none !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-set-upload.has-images .ecom-quick-upload-thumbs {
display: contents !important;
width: auto !important;
max-width: none !important;
padding: 0 !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-set-upload.has-images .ecom-command-asset-thumb.ecom-quick-upload-thumb {
position: relative !important;
flex: 0 0 64px !important;
width: 64px !important;
height: 64px !important;
min-width: 64px !important;
min-height: 64px !important;
margin: 0 !important;
overflow: hidden !important;
border: 1px solid #e8edf0 !important;
border-radius: 8px !important;
background: #f6f8fa !important;
box-shadow: none !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-set-upload.has-images .ecom-command-asset-thumb.ecom-quick-upload-thumb > img {
display: block !important;
width: 100% !important;
height: 100% !important;
border-radius: 7px !important;
object-fit: cover !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-set-upload.has-images .ecom-command-asset-thumb.ecom-quick-upload-thumb > button {
position: absolute !important;
top: 4px !important;
right: 4px !important;
z-index: 6 !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
width: 22px !important;
height: 22px !important;
min-width: 22px !important;
min-height: 22px !important;
padding: 0 !important;
border: 1px solid rgba(239, 68, 68, 0.42) !important;
border-radius: 999px !important;
color: #ef4444 !important;
background: rgba(255, 255, 255, 0.92) !important;
box-shadow: 0 8px 18px rgba(239, 68, 68, 0.16) !important;
cursor: pointer !important;
opacity: 0 !important;
pointer-events: none !important;
transform: scale(0.92) !important;
visibility: hidden !important;
transition:
opacity 150ms ease,
transform 150ms ease,
background 150ms ease,
box-shadow 150ms ease,
visibility 150ms ease !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-set-upload.has-images .ecom-command-asset-thumb.ecom-quick-upload-thumb:hover > button,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-set-upload.has-images .ecom-command-asset-thumb.ecom-quick-upload-thumb:focus-within > button {
opacity: 1 !important;
pointer-events: auto !important;
transform: scale(1) !important;
visibility: visible !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-set-upload.has-images .ecom-command-asset-thumb.ecom-quick-upload-thumb > button:hover {
border-color: rgba(220, 38, 38, 0.72) !important;
color: #dc2626 !important;
background: #fff1f2 !important;
box-shadow: 0 10px 22px rgba(220, 38, 38, 0.22) !important;
transform: scale(1.04) !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-set-upload.has-images .ecom-command-asset-thumb.ecom-quick-upload-thumb > button .anticon {
display: inline-flex !important;
font-size: 13px !important;
line-height: 1 !important;
color: currentColor !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-set-upload.has-images .ecom-quick-hot-add-btn {
flex: 0 0 64px !important;
width: 64px !important;
height: 64px !important;
min-width: 64px !important;
min-height: 64px !important;
border: 1px solid #e8edf0 !important;
border-radius: 8px !important;
color: #111827 !important;
background: #f3f4f6 !important;
box-shadow: none !important;
font-size: 22px !important;
transform: none !important;
}
/* Keep hot material upload controls visible after files are added. */
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-hot-material.has-images {
display: flex !important;
flex-wrap: wrap !important;
align-items: flex-start !important;
justify-content: flex-start !important;
gap: 10px !important;
min-height: 0 !important;
height: auto !important;
padding: 10px !important;
overflow: visible !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-hot-material.has-images .ecom-quick-upload-thumbs {
display: contents !important;
width: auto !important;
max-width: none !important;
padding: 0 !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-hot-material.has-images .ecom-command-asset-thumb.ecom-quick-upload-thumb {
flex: 0 0 64px !important;
width: 64px !important;
height: 64px !important;
min-width: 64px !important;
min-height: 64px !important;
overflow: visible !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-hot-material.has-images .ecom-command-asset-thumb.ecom-quick-upload-thumb > img {
overflow: hidden !important;
border-radius: 8px !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-hot-material.has-images .ecom-command-asset-thumb.ecom-quick-upload-thumb > button {
top: -7px !important;
right: -7px !important;
width: 22px !important;
height: 22px !important;
min-width: 22px !important;
min-height: 22px !important;
opacity: 1 !important;
visibility: visible !important;
pointer-events: auto !important;
transform: none !important;
color: #ef4444 !important;
background: #ffffff !important;
border: 1px solid rgba(239, 68, 68, 0.5) !important;
box-shadow: 0 8px 18px rgba(239, 68, 68, 0.16) !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-hot-material.has-images .ecom-command-asset-thumb.ecom-quick-upload-thumb > button .anticon,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-hot-material.has-images .ecom-command-asset-thumb.ecom-quick-upload-thumb > button svg {
display: block !important;
width: 12px !important;
height: 12px !important;
color: currentColor !important;
fill: none !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-hot-material.has-images .ecom-quick-hot-add-btn {
display: inline-flex !important;
flex: 0 0 64px !important;
width: 64px !important;
height: 64px !important;
min-width: 64px !important;
min-height: 64px !important;
margin: 0 !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-hot-material.has-images .ecom-command-asset-thumb.ecom-quick-upload-thumb > button.ecom-hot-material-delete {
position: absolute !important;
top: -8px !important;
right: -8px !important;
z-index: 20 !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
width: 24px !important;
height: 24px !important;
min-width: 24px !important;
min-height: 24px !important;
padding: 0 !important;
overflow: visible !important;
border: 1px solid rgba(239, 68, 68, 0.62) !important;
border-radius: 999px !important;
color: #ef4444 !important;
background: #ffffff !important;
box-shadow: 0 8px 18px rgba(239, 68, 68, 0.16) !important;
cursor: pointer !important;
opacity: 1 !important;
pointer-events: auto !important;
transform: none !important;
visibility: visible !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-hot-material.has-images .ecom-command-asset-thumb.ecom-quick-upload-thumb > button.ecom-hot-material-delete:hover {
border-color: #dc2626 !important;
color: #dc2626 !important;
background: #fff1f2 !important;
box-shadow: 0 10px 22px rgba(220, 38, 38, 0.24) !important;
transform: scale(1.04) !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-hot-material.has-images .ecom-command-asset-thumb.ecom-quick-upload-thumb > button.ecom-hot-material-delete svg {
display: block !important;
width: 14px !important;
height: 14px !important;
stroke: currentColor !important;
stroke-width: 1.9 !important;
stroke-linecap: round !important;
stroke-linejoin: round !important;
fill: none !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-hot-material:not(.has-images) {
min-height: 94px !important;
padding: 12px 14px !important;
gap: 6px !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-hot-reference.has-images > .anticon,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-hot-reference.has-images > span,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-hot-reference.has-images > em,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-hot-page .ecom-quick-hot-reference.has-images > b {
display: none !important;
}
/* Generation record detail workspace: left chat, center canvas, right history drawer. */
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"].is-history-detail {
--clone-chat-width: 352px;
+1
View File
@@ -0,0 +1 @@
export * from "./types.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./enterpriseVideoPolicy.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./happyHorseRouting.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./pixverseRouting.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./resolveVideoModel.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./taskLifecycle.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./translateTaskError.ts";
+1
View File
@@ -0,0 +1 @@
export * from "./viduRouting.ts";