6 Commits

Author SHA1 Message Date
ludan 2cd76ec3a5 feat: add composer toolbelt with asset library, work mode selector, AI-powered prompt writing, and scenario settings
- EcommercePage.tsx (+260):
  - Add ComposerAssetTabKey and ComposerWorkModeKey types; extend ComposerMenuKey with assetLibrary/workMode/aiWrite
  - Add composerTooltip/composerAssetTab/composerWorkMode/aiWriteDraft state
  - Add composerAssetTabs (最近保存/套图配方/模特库), composerWorkModeOptions (快捷/思考), and composerRatioOptions (7 presets with display dimensions)
  - Add scenarioSettingsKeys and scenarioAdvancedSettingsKeys for conditional settings panel display
  - Add PaperPlaneRight icon import for AI writing send button
  - Reorder salesVideo tag position in commerceScenarioOptions; emoji icons replace Ant Design icons
  - handleCommerceScenarioClick: second click on active scenario now deselects (sets null) instead of toggling visibility
  - shouldShowScenarioSettings: settings panel visible for poster/mainImage/model/scene/festival/salesVideo but not popular
  - renderComposerAssetPanel: asset library popover with tab selector (recent/recipe/model) and grid display
  - renderComposerWorkModePanel: work mode radio popover with description cards
  - renderComposerAiWritePanel: AI prompt auto-complete panel with text input and send button; applyAiWriteSuggestion merges keyword + mode hint + platform context into composer prompt
  - Toolbar restructured with .ecom-command-tool pill buttons (upload/assets/mode/AI write) in .ecom-command-composer-actions

- ecommerce-standalone.css (+937):
  - Composer toolbar: horizontal flex row with space-between, overflow-x scroll with hidden scrollbar
  - .ecom-command-tool: 40px pill-shaped buttons with gradient backgrounds, hover/active/dragging states with glow transition and lift
  - .ecom-command-tool--upload: icon+label layout for upload button
  - .ecom-command-tool--icon: 40px square icon-only button variant
  - Asset panel: tab selector row, 3-column recipe grid with aspect-ratio cards, hover scale effect
  - Work mode panel: radio-style card selector with description text
  - AI write panel: text input area with send button, responsive sizing
  - Tooltip: positioned above toolbar buttons with arrow pointer

- pages/ecommerce.css (+490):
  - Composer input focus-within: green glow border + deepened shadow + lift transition
  - Asset library, work mode, AI write panel styles with consistent tokenized spacing and transitions

- standalone/overrides.css (+7):
  - ≤420px settings option row: switch from grid to flex with flex:1 on buttons for tight viewport fit
2026-06-17 14:52:42 +08:00
stringadmin 7fa51ff90a Merge pull request 'Codex/main latest 20260615 030000' (#24) from codex/main-latest-20260615-030000 into main
Reviewed-on: #24
2026-06-17 03:20:02 +00:00
Codex 2c3c6eb2c9 Merge remote-tracking branch 'origin/main' into codex/main-latest-20260615-030000
# Conflicts:
#	src/styles/ecommerce-standalone.css
2026-06-17 11:04:26 +08:00
stringadmin d83ad25be3 Merge pull request 'feat: enhance scenario tabs with more/expand toggle, template carousel navigation, and 16 new templates' (#23) from feat/ecommerce-scenario-tabs into main
Reviewed-on: #23
2026-06-17 02:16:55 +00:00
Codex eb7b769155 Merge remote-tracking branch 'origin/main' into codex/main-latest-20260615-030000
# Conflicts:
#	src/styles/ecommerce-standalone.css
2026-06-16 23:28:07 +08:00
Codex ad38a4a0e3 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
2026-06-16 21:47:07 +08:00
5 changed files with 2728 additions and 75 deletions
+633 -40
View File
@@ -32,6 +32,7 @@ import {
Gift, Gift,
MagicWand, MagicWand,
Mountains, Mountains,
PaperPlaneRight,
ShoppingBag, ShoppingBag,
User, User,
VideoCamera, VideoCamera,
@@ -50,6 +51,7 @@ import EcommerceDetailPanel from "./panels/EcommerceDetailPanel";
import EcommerceSetPanel from "./panels/EcommerceSetPanel"; import EcommerceSetPanel from "./panels/EcommerceSetPanel";
import EcommerceTryOnPanel from "./panels/EcommerceTryOnPanel"; import EcommerceTryOnPanel from "./panels/EcommerceTryOnPanel";
import EcommerceClonePanel from "./panels/EcommerceClonePanel"; import EcommerceClonePanel from "./panels/EcommerceClonePanel";
import EcommerceCopywritingPanel from "./panels/EcommerceCopywritingPanel";
import { ecommerceOssScopes, saveUnifiedEcommerceGenerationRecord, deleteEcommerceGenerationRecord } from "./ecommerceGenerationPersistence"; import { ecommerceOssScopes, saveUnifiedEcommerceGenerationRecord, deleteEcommerceGenerationRecord } from "./ecommerceGenerationPersistence";
import { downloadResultAsset } from "../workbench/workbenchDownload"; import { downloadResultAsset } from "../workbench/workbenchDownload";
import type { CloneOutputKey, ProductSetOutputKey } from "./utils/platformRules"; import type { CloneOutputKey, ProductSetOutputKey } from "./utils/platformRules";
@@ -288,7 +290,9 @@ type CloneModelPanelTab = "scene" | "model";
type CloneVideoQualityKey = "standard" | "high" | "ultra"; type CloneVideoQualityKey = "standard" | "high" | "ultra";
type ProductSetStatus = "idle" | "ready" | "generating" | "done" | "failed"; type ProductSetStatus = "idle" | "ready" | "generating" | "done" | "failed";
type ProductKitToolKey = "set" | "detail" | "wear" | "clone"; type ProductKitToolKey = "set" | "detail" | "wear" | "clone";
type ComposerMenuKey = "mode" | "platform" | "language" | "ratio" | "settings"; type ComposerMenuKey = "mode" | "platform" | "language" | "ratio" | "settings" | "assetLibrary" | "workMode" | "aiWrite";
type ComposerAssetTabKey = "recent" | "recipe" | "model";
type ComposerWorkModeKey = "quick" | "think";
type CloneBasicSelectKey = "platform" | "market" | "language" | "ratio"; type CloneBasicSelectKey = "platform" | "market" | "language" | "ratio";
type CloneModelSelectKey = "gender" | "age" | "ethnicity" | "body"; type CloneModelSelectKey = "gender" | "age" | "ethnicity" | "body";
type CloneReferenceMode = "upload" | "link"; type CloneReferenceMode = "upload" | "link";
@@ -1061,11 +1065,13 @@ const commerceScenarioOptions: Array<{ key: CommerceScenarioKey; label: string;
{ key: "model", label: "模特图", desc: "真人展示", icon: <span role="img" aria-label="model">🕴</span> }, { key: "model", label: "模特图", desc: "真人展示", icon: <span role="img" aria-label="model">🕴</span> },
{ key: "scene", label: "场景图", desc: "生活氛围", icon: <span role="img" aria-label="scene">🌅</span> }, { key: "scene", label: "场景图", desc: "生活氛围", icon: <span role="img" aria-label="scene">🌅</span> },
{ key: "festival", label: "节日风格图", desc: "节点营销", icon: <span role="img" aria-label="festival">🎉</span> }, { key: "festival", label: "节日风格图", desc: "节点营销", icon: <span role="img" aria-label="festival">🎉</span> },
{ key: "salesVideo", label: "带货视频", desc: "短视频脚本", icon: <span role="img" aria-label="video">🎬</span> },
{ key: "background", label: "更换背景", desc: "背景重构", icon: <span role="img" aria-label="background"></span> }, { key: "background", label: "更换背景", desc: "背景重构", icon: <span role="img" aria-label="background"></span> },
{ key: "retouch", label: "无痕改图", desc: "精修优化", icon: <span role="img" aria-label="retouch">🪄</span> }, { key: "retouch", label: "无痕改图", desc: "精修优化", icon: <span role="img" aria-label="retouch">🪄</span> },
{ key: "salesVideo", label: "带货视频", desc: "短视频脚本", icon: <span role="img" aria-label="video">🎬</span> },
]; ];
const primaryCommerceScenarioKeys: CommerceScenarioKey[] = ["popular", "poster", "mainImage", "model"]; const primaryCommerceScenarioKeys: CommerceScenarioKey[] = ["popular", "poster", "mainImage", "model"];
const scenarioSettingsKeys: CommerceScenarioKey[] = ["poster", "mainImage", "model", "scene", "festival", "salesVideo"];
const scenarioAdvancedSettingsKeys: CommerceScenarioKey[] = ["model", "salesVideo"];
const commerceScenarioOutputMap: Record<Exclude<CommerceScenarioKey, "popular">, ProductSetOutputKey> = { const commerceScenarioOutputMap: Record<Exclude<CommerceScenarioKey, "popular">, ProductSetOutputKey> = {
poster: "set", poster: "set",
mainImage: "set", mainImage: "set",
@@ -1884,7 +1890,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const [selectedProductSetPreview, setSelectedProductSetPreview] = useState<ProductSetPreviewSelection | null>(null); const [selectedProductSetPreview, setSelectedProductSetPreview] = useState<ProductSetPreviewSelection | null>(null);
const [showHostingModal, setShowHostingModal] = useState(false); const [showHostingModal, setShowHostingModal] = useState(false);
const [productImages, setProductImages] = useState<CloneImageItem[]>([]); 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 [smartCutoutImage, setSmartCutoutImage] = useState<SmartCutoutImageItem | null>(null);
const [smartCutoutBatchImages, setSmartCutoutBatchImages] = useState<SmartCutoutImageItem[]>([]); const [smartCutoutBatchImages, setSmartCutoutBatchImages] = useState<SmartCutoutImageItem[]>([]);
const [smartCutoutBackgroundColor, setSmartCutoutBackgroundColor] = useState("#ffffff"); const [smartCutoutBackgroundColor, setSmartCutoutBackgroundColor] = useState("#ffffff");
@@ -1936,6 +1942,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const [isComposerMenuClosing, setIsComposerMenuClosing] = useState(false); const [isComposerMenuClosing, setIsComposerMenuClosing] = useState(false);
const [composerPopoverLeft, setComposerPopoverLeft] = useState(0); const [composerPopoverLeft, setComposerPopoverLeft] = useState(0);
const [composerPopoverTop, setComposerPopoverTop] = useState(0); const [composerPopoverTop, setComposerPopoverTop] = useState(0);
const [composerTooltip, setComposerTooltip] = useState<{ text: string; left: number; top: number } | null>(null);
const [composerAssetTab, setComposerAssetTab] = useState<ComposerAssetTabKey>("recent");
const [composerWorkMode, setComposerWorkMode] = useState<ComposerWorkModeKey>("quick");
const [aiWriteDraft, setAiWriteDraft] = useState("");
const [isCommandHistoryCollapsed, setIsCommandHistoryCollapsed] = useState(true); const [isCommandHistoryCollapsed, setIsCommandHistoryCollapsed] = useState(true);
const [inspirationPreview, setInspirationPreview] = useState<{ mediaUrl: string; mediaType: "image" | "video"; prompt: string } | null>(null); const [inspirationPreview, setInspirationPreview] = useState<{ mediaUrl: string; mediaType: "image" | "video"; prompt: string } | null>(null);
const [isQuickPanelCollapsed, setIsQuickPanelCollapsed] = useState(false); const [isQuickPanelCollapsed, setIsQuickPanelCollapsed] = useState(false);
@@ -1959,6 +1969,11 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const [cloneVideoSmart, setCloneVideoSmart] = useState(true); const [cloneVideoSmart, setCloneVideoSmart] = useState(true);
const [isCloneSettingsCollapsed, setIsCloneSettingsCollapsed] = useState(false); const [isCloneSettingsCollapsed, setIsCloneSettingsCollapsed] = useState(false);
const [isCloneConversationCollapsed, setIsCloneConversationCollapsed] = 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 [previewZoom, setPreviewZoom] = useState(1);
const quickSetSelectTimerRef = useRef<number | null>(null); const quickSetSelectTimerRef = useRef<number | null>(null);
const openQuickSetSelectRef = useRef<CloneBasicSelectKey | null>(null); const openQuickSetSelectRef = useRef<CloneBasicSelectKey | null>(null);
@@ -2394,6 +2409,18 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
[cloneOutput, platform], [cloneOutput, platform],
); );
const cloneRatioOptions = baseCloneRatioOptions; const cloneRatioOptions = baseCloneRatioOptions;
const composerRatioOptions = useMemo(
() => [
"1000×1000px 1:1",
"800×1200px 2:3",
"1200×800px 3:2",
"1200×900px 4:3",
"900×1200px 3:4",
"1080×1920px 9:16",
"1920×1080px 16:9",
],
[],
);
const productSetLanguageOptions = useMemo( const productSetLanguageOptions = useMemo(
() => getPlatformLanguageOptions(productSetPlatform, productSetMarket), () => getPlatformLanguageOptions(productSetPlatform, productSetMarket),
[productSetMarket, productSetPlatform], [productSetMarket, productSetPlatform],
@@ -2439,6 +2466,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
: activeCommerceScenario === "popular" : activeCommerceScenario === "popular"
? popularCommerceScenarioTemplates ? popularCommerceScenarioTemplates
: commerceScenarioTemplates.filter((template) => template.scenario === activeCommerceScenario); : commerceScenarioTemplates.filter((template) => template.scenario === activeCommerceScenario);
const shouldShowScenarioSettings = activeCommerceScenario !== null && scenarioSettingsKeys.includes(activeCommerceScenario);
useEffect(() => { useEffect(() => {
templateStripRef.current?.scrollTo({ left: 0, behavior: "auto" }); templateStripRef.current?.scrollTo({ left: 0, behavior: "auto" });
}, [activeCommerceScenario, isCloneTemplateStripVisible]); }, [activeCommerceScenario, isCloneTemplateStripVisible]);
@@ -2456,6 +2484,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const canGenerateTryOn = garmentImages.length > 0 && tryOnStatus !== "generating" && tryOnStatus !== "modeling"; const canGenerateTryOn = garmentImages.length > 0 && tryOnStatus !== "generating" && tryOnStatus !== "modeling";
const canGenerateDetail = detailProductImages.length > 0 && detailStatus !== "generating"; const canGenerateDetail = detailProductImages.length > 0 && detailStatus !== "generating";
const canGenerateHot = cloneReferenceImages.length > 0 && hotStatus !== "generating"; const canGenerateHot = cloneReferenceImages.length > 0 && hotStatus !== "generating";
const canGenerateQuickSet = productImages.length > 0 && quickSetStatus !== "generating";
const cloneVideoDurationProgress = const cloneVideoDurationProgress =
((cloneVideoDuration - cloneVideoDurationMin) / (cloneVideoDurationMax - cloneVideoDurationMin)) * 100; ((cloneVideoDuration - cloneVideoDurationMin) / (cloneVideoDurationMax - cloneVideoDurationMin)) * 100;
const cloneVideoDurationStyle: CSSProperties = useMemo( const cloneVideoDurationStyle: CSSProperties = useMemo(
@@ -3837,7 +3866,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const handleCommerceScenarioClick = (nextScenario: CommerceScenarioKey) => { const handleCommerceScenarioClick = (nextScenario: CommerceScenarioKey) => {
if (nextScenario === activeCommerceScenario) { if (nextScenario === activeCommerceScenario) {
setIsCloneTemplateStripVisible((visible) => !visible); setActiveCommerceScenario(null);
setIsCloneTemplateStripVisible(false);
setComposerMenu(null); setComposerMenu(null);
return; return;
} }
@@ -4810,6 +4840,12 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
); );
}; };
const handleQuickSetAiWrite = () => {
setQuickSetRequirement(
"1.产品名称:无线降噪蓝牙耳机\n2.核心卖点:主动降噪、24H续航、低延迟连接、舒适佩戴\n3.适用人群:通勤、办公、运动和旅行用户\n4.期望场景:地铁通勤、居家办公、户外运动\n5.具体参数:蓝牙5.3、IPX4防水、快充10分钟使用2小时",
);
};
const stopHotProgress = () => { const stopHotProgress = () => {
if (hotProgressRef.current !== null) { if (hotProgressRef.current !== null) {
window.clearInterval(hotProgressRef.current); window.clearInterval(hotProgressRef.current);
@@ -4817,6 +4853,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 = () => { const startHotProgress = () => {
stopHotProgress(); stopHotProgress();
setHotProgress(0); setHotProgress(0);
@@ -4854,6 +4911,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 handleHotMaterialMouseEnter = (src: string, event: ReactMouseEvent<HTMLElement>) => {
const rect = event.currentTarget.getBoundingClientRect(); const rect = event.currentTarget.getBoundingClientRect();
const previewHalfWidth = 150; const previewHalfWidth = 150;
@@ -4918,6 +5011,37 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
setComposerMenu(null); 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 = () => { const resetTask = () => {
setSetImages([]); setSetImages([]);
setProductSetRequirement(""); setProductSetRequirement("");
@@ -4981,6 +5105,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const isImageEditTool = isCloneTool && activeQuickTool === "image-edit"; const isImageEditTool = isCloneTool && activeQuickTool === "image-edit";
const isTranslateTool = isCloneTool && activeQuickTool === "translate"; const isTranslateTool = isCloneTool && activeQuickTool === "translate";
const isHotCloneTool = isCloneTool && activeQuickTool === "hot"; 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 pageLabel = isSetTool ? "商品套图" : isDetail ? "A+/详情页" : isTryOn ? "AI服饰穿戴" : activeToolMeta?.label || "商品工具";
const setPrimaryLabel = const setPrimaryLabel =
setImages.length === 0 setImages.length === 0
@@ -5406,6 +5532,19 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
{ key: "ratio", label: "尺寸/比例", value: getQuickSetRatioValue(hotRatio), options: quickSetRatioOptions, onChange: setHotRatio }, { 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<{ const cloneModelSelects: Array<{
key: CloneModelSelectKey; key: CloneModelSelectKey;
label: string; label: string;
@@ -5687,6 +5826,96 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
? String(cloneVideoDuration) + "秒 " + (cloneVideoQuality === "standard" ? "720P" : "1080P") ? String(cloneVideoDuration) + "秒 " + (cloneVideoQuality === "standard" ? "720P" : "1080P")
: "换装素材"; : "换装素材";
const composerAssetTabs: Array<{ key: ComposerAssetTabKey; label: string }> = [
{ key: "recent", label: "最近保存" },
{ key: "recipe", label: "套图配方" },
{ key: "model", label: "模特库" },
];
const composerWorkModeOptions: Array<{ key: ComposerWorkModeKey; label: string; desc: string }> = [
{ key: "quick", label: "快捷", desc: "快速整理提示词,适合常规商品图生成。" },
{ key: "think", label: "思考", desc: "更强调卖点拆解、场景规划和图文一致性。" },
];
const applyAiWriteSuggestion = () => {
const keyword = aiWriteDraft.trim();
if (!keyword) {
toast.info("请输入产品关键词或卖点");
return;
}
const modeHint = composerWorkMode === "think" ? "先拆解目标人群、核心卖点和使用场景," : "";
const nextValue = `${keyword}${modeHint}请生成适合${platform}的高转化电商素材,画面干净高级,突出产品主体、核心卖点、使用场景和购买理由。`.slice(0, 500);
setRequirement(nextValue);
setComposerMenu(null);
};
const renderComposerAssetPanel = () => {
const renderEmpty = (label: string) => (
<div className="ecom-command-library-empty">
<FolderOpenOutlined />
<strong></strong>
<span>{label}</span>
</div>
);
let content: ReactNode;
if (composerAssetTab === "recent") {
content = ecommerceHistoryRecords.length ? (
<div className="ecom-command-library-list">
{ecommerceHistoryRecords.slice(0, 4).map((record) => {
const outputLabel = cloneOutputOptions.find((option) => option.key === record.output)?.label || "生成记录";
return (
<button key={record.id} type="button" onClick={() => { openEcommerceHistoryRecord(record); setComposerMenu(null); }}>
<strong>{record.title}</strong>
<span>{outputLabel} · {formatHistoryTime(record.createdAt)}</span>
</button>
);
})}
</div>
) : renderEmpty("生成后保存的素材会沉淀在这里");
} else if (composerAssetTab === "recipe") {
content = (
<div className="ecom-command-library-list">
{commerceScenarioTemplates.slice(0, 4).map((template) => (
<button key={template.id} type="button" onClick={() => { handleCloneTemplateCardClick(template); setComposerMenu(null); }}>
<strong>{template.title}</strong>
<span>{template.badge} · {template.desc}</span>
</button>
))}
</div>
);
} else {
content = (
<div className="ecom-command-library-list ecom-command-library-list--model">
{tryOnScenes.slice(0, 4).map((scene) => (
<button key={scene} type="button" className={selectedCloneModelScenes.includes(scene) ? "is-active" : ""} onClick={() => toggleCloneModelScene(scene)}>
<strong>{scene}</strong>
<span></span>
</button>
))}
</div>
);
}
return (
<>
<header className="ecom-command-library-head">
<strong></strong>
<button type="button" className="ecom-command-library-help" onClick={(event) => event.preventDefault()}>
<QuestionCircleOutlined /> 使
</button>
</header>
<div className="ecom-command-library-tabs" role="tablist" aria-label="资产库分类">
{composerAssetTabs.map((tab) => (
<button key={tab.key} type="button" className={composerAssetTab === tab.key ? "is-active" : ""} onClick={() => setComposerAssetTab(tab.key)}>
{tab.label}
</button>
))}
</div>
{content}
</>
);
};
const renderComposerMenu = () => { const renderComposerMenu = () => {
const composerLanguageOptions = Array.from(new Set(marketLanguageOptions.flatMap((option) => option.languages))).map((item) => ({ const composerLanguageOptions = Array.from(new Set(marketLanguageOptions.flatMap((option) => option.languages))).map((item) => ({
language: item, language: item,
@@ -5700,6 +5929,37 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
if (!menuToRender) return null; if (!menuToRender) return null;
const popoverClosingClass = !composerMenu && isComposerMenuClosing ? " is-closing" : ""; const popoverClosingClass = !composerMenu && isComposerMenuClosing ? " is-closing" : "";
const composerPopoverKey = `${menuToRender}-${cloneOutput}-${popoverClosingClass ? "closing" : "open"}`; const composerPopoverKey = `${menuToRender}-${cloneOutput}-${popoverClosingClass ? "closing" : "open"}`;
if (menuToRender === "assetLibrary") {
return (
<div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--library${popoverClosingClass}`} style={composerPopoverStyle}>
{renderComposerAssetPanel()}
</div>
);
}
if (menuToRender === "workMode") {
return (
<div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--work-mode${popoverClosingClass}`} style={composerPopoverStyle}>
<header><strong></strong><span></span></header>
{composerWorkModeOptions.map((option) => (
<button key={option.key} type="button" className={composerWorkMode === option.key ? "is-active" : ""} onClick={() => { setComposerWorkMode(option.key); setComposerMenu(null); }}>
<strong>{option.label}</strong>
<span>{option.desc}</span>
</button>
))}
</div>
);
}
if (menuToRender === "aiWrite") {
return (
<div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--ai-write${popoverClosingClass}`} style={composerPopoverStyle}>
<header><strong>AI </strong><span></span></header>
<textarea value={aiWriteDraft} onChange={(event) => setAiWriteDraft(event.target.value.slice(0, 120))} placeholder="输入产品名称、卖点或期望风格" />
<button type="button" className="ecom-command-ai-submit" onClick={applyAiWriteSuggestion}>
</button>
</div>
);
}
if (menuToRender === "mode") { if (menuToRender === "mode") {
return ( return (
<div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--grid ecom-command-popover--mode${popoverClosingClass}`} style={composerPopoverStyle}> <div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--grid ecom-command-popover--mode${popoverClosingClass}`} style={composerPopoverStyle}>
@@ -5730,8 +5990,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
<div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--languages${popoverClosingClass}`} style={composerPopoverStyle}> <div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--languages${popoverClosingClass}`} style={composerPopoverStyle}>
{composerLanguageOptions.map((option) => ( {composerLanguageOptions.map((option) => (
<button key={option.language} type="button" className={language === option.language ? "is-active" : ""} onClick={() => { setLanguage(option.language); setComposerMenu(null); }}> <button key={option.language} type="button" className={language === option.language ? "is-active" : ""} onClick={() => { setLanguage(option.language); setComposerMenu(null); }}>
<strong>{option.language}</strong> {option.language}
<span>({option.countries.slice(0, 3).join(" / ")}{option.countries.length > 3 ? " +" + String(option.countries.length - 3) : ""})</span>
</button> </button>
))} ))}
</div> </div>
@@ -5740,17 +5999,11 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
if (menuToRender === "ratio") { if (menuToRender === "ratio") {
return ( return (
<div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--list ecom-command-popover--ratio-picker${popoverClosingClass}`} style={composerPopoverStyle}> <div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--list ecom-command-popover--ratio-picker${popoverClosingClass}`} style={composerPopoverStyle}>
{cloneRatioOptions.map((option) => { {composerRatioOptions.map((option) => {
const ratioParts = getRatioDisplayParts(option); const ratioParts = getRatioDisplayParts(option);
return ( return (
<button key={option} type="button" className={ratio === option ? "is-active" : ""} onClick={() => { setRatio(option); setComposerMenu(null); }}> <button key={option} type="button" className={ratio === option ? "is-active" : ""} onClick={() => { setRatio(option); setComposerMenu(null); }}>
<span className="ecom-command-ratio-icon" aria-hidden="true"> {ratioParts.aspect}
<TableOutlined />
</span>
<span className="ecom-command-ratio-copy">
<strong>{ratioParts.size}</strong>
<em>{ratioParts.aspect}</em>
</span>
</button> </button>
); );
})} })}
@@ -6344,24 +6597,28 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
))} ))}
</div> </div>
) : null} ) : null}
<div className="ecom-command-option-row ecom-command-option-row--settings"> {shouldShowScenarioSettings ? (
<button type="button" className={composerMenu === "platform" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("platform", event)}> <div className="ecom-command-option-row ecom-command-option-row--settings">
<span className="ecom-command-option-icon" aria-hidden="true"><GlobalOutlined /></span> <button type="button" className={composerMenu === "platform" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("platform", event)}>
<span></span>{platform} <span className="ecom-command-option-icon" aria-hidden="true"><GlobalOutlined /></span>
</button> <span></span>{platform}
<button type="button" className={composerMenu === "language" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("language", event)}> </button>
<span className="ecom-command-option-icon" aria-hidden="true"><FileImageOutlined /></span> <button type="button" className={composerMenu === "language" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("language", event)}>
<span></span>{language} <span className="ecom-command-option-icon" aria-hidden="true"><FileImageOutlined /></span>
</button> <span></span>{language}
<button type="button" className={composerMenu === "ratio" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("ratio", event)}> </button>
<span className="ecom-command-option-icon" aria-hidden="true"><TableOutlined /></span> <button type="button" className={composerMenu === "ratio" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("ratio", event)}>
<span></span>{formatRatioDisplayValue(ratio)} <span className="ecom-command-option-icon" aria-hidden="true"><TableOutlined /></span>
</button> <span></span>{getRatioDisplayParts(ratio).aspect}
<button type="button" className={composerMenu === "settings" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("settings", event)}> </button>
<span className="ecom-command-option-icon" aria-hidden="true"><SettingOutlined /></span> {scenarioAdvancedSettingsKeys.includes(activeCommerceScenario) ? (
<span></span>{composerSettingLabel} <button type="button" className={composerMenu === "settings" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("settings", event)}>
</button> <span className="ecom-command-option-icon" aria-hidden="true"><SettingOutlined /></span>
</div> <span></span>{composerSettingLabel}
</button>
) : null}
</div>
) : null}
<textarea <textarea
ref={requirementTextareaRef} ref={requirementTextareaRef}
value={requirement} value={requirement}
@@ -6385,7 +6642,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
<div className="ecom-command-composer-actions"> <div className="ecom-command-composer-actions">
<button <button
type="button" type="button"
className={`ecom-command-reference ecom-command-reference--bottom${productImages.length ? " has-images" : ""}${isProductUploadDragging ? " is-dragging" : ""}`} className={`ecom-command-reference ecom-command-reference--bottom ecom-command-tool ecom-command-tool--upload${productImages.length ? " has-images" : ""}${isProductUploadDragging ? " is-dragging" : ""}`}
onClick={() => productInputRef.current?.click()} onClick={() => productInputRef.current?.click()}
onDragEnter={(event) => { onDragEnter={(event) => {
event.preventDefault(); event.preventDefault();
@@ -6399,18 +6656,71 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const files = Array.from(event.dataTransfer.files); const files = Array.from(event.dataTransfer.files);
if (files.length) addComposerAssets(files); if (files.length) addComposerAssets(files);
}} }}
aria-label="上传素材"
title="上传素材"
> >
<span aria-hidden="true"><PaperClipOutlined /></span> <span aria-hidden="true"><PaperClipOutlined /></span>
<strong></strong> <strong></strong>
</button> </button>
<button
type="button"
className={`ecom-command-tool ecom-command-tool--icon${composerMenu === "assetLibrary" ? " is-active" : ""}`}
onClick={(event) => toggleComposerMenu("assetLibrary", event)}
onMouseEnter={(event) => {
const rect = event.currentTarget.getBoundingClientRect();
setComposerTooltip({ text: "资产库", left: rect.left + rect.width / 2, top: rect.top - 8 });
}}
onMouseLeave={() => setComposerTooltip(null)}
aria-label="资产库"
title="资产库"
data-tooltip="资产库"
>
<FolderOpenOutlined />
</button>
<button
type="button"
className={`ecom-command-tool ecom-command-tool--icon${composerMenu === "workMode" ? " is-active" : ""}`}
onClick={(event) => toggleComposerMenu("workMode", event)}
onMouseEnter={(event) => {
const rect = event.currentTarget.getBoundingClientRect();
setComposerTooltip({ text: `模式:${composerWorkMode === "quick" ? "快捷" : "思考"}`, left: rect.left + rect.width / 2, top: rect.top - 8 });
}}
onMouseLeave={() => setComposerTooltip(null)}
aria-label={`模式:${composerWorkMode === "quick" ? "快捷" : "思考"}`}
title={`模式:${composerWorkMode === "quick" ? "快捷" : "思考"}`}
data-tooltip={`模式:${composerWorkMode === "quick" ? "快捷" : "思考"}`}
>
<ArrowsCounterClockwise size={17} weight="bold" />
</button>
<button
type="button"
className={`ecom-command-tool ecom-command-tool--icon${composerMenu === "aiWrite" ? " is-active" : ""}`}
onClick={(event) => toggleComposerMenu("aiWrite", event)}
onMouseEnter={(event) => {
const rect = event.currentTarget.getBoundingClientRect();
setComposerTooltip({ text: "AI帮写", left: rect.left + rect.width / 2, top: rect.top - 8 });
}}
onMouseLeave={() => setComposerTooltip(null)}
aria-label="AI帮写"
title="AI帮写"
data-tooltip="AI帮写"
>
<MagicWand size={17} weight="bold" />
</button>
</div> </div>
<div className="ecom-command-submit-row"> <div className="ecom-command-submit-row">
<button type="button" className="clone-ai-send-button ecom-command-send" disabled={commandGenerateDisabled} onClick={handleCommandGenerate} aria-label={clonePrimaryLabel}> <button type="button" className="clone-ai-send-button ecom-command-send" disabled={commandGenerateDisabled} onClick={handleCommandGenerate} aria-label={clonePrimaryLabel}>
{status === "generating" ? <LoadingOutlined /> : "➤"} {status === "generating" ? <LoadingOutlined /> : <PaperPlaneRight size={18} weight="fill" />}
</button> </button>
</div> </div>
</div> </div>
{renderComposerMenu()} {renderComposerMenu()}
{composerTooltip ? createPortal(
<span className="ecom-command-tooltip-floating" style={{ left: composerTooltip.left, top: composerTooltip.top }}>
{composerTooltip.text}
</span>,
document.body
) : null}
</div> </div>
{(status === "idle" || status === "ready") && !showMainVideoWorkspace && activeCommerceScenario !== null && isCloneTemplateStripVisible ? ( {(status === "idle" || status === "ready") && !showMainVideoWorkspace && activeCommerceScenario !== null && isCloneTemplateStripVisible ? (
<div className={`ecom-command-template-carousel ecom-command-template-carousel--${activeCommerceScenario}`}> <div className={`ecom-command-template-carousel ecom-command-template-carousel--${activeCommerceScenario}`}>
@@ -6469,15 +6779,23 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
{ label: "智能抠图", tone: "cutout", icon: <ScissorOutlined />, onClick: openSmartCutoutUpload }, { label: "智能抠图", tone: "cutout", icon: <ScissorOutlined />, onClick: openSmartCutoutUpload },
{ label: "去除水印", tone: "watermark", icon: <ClearOutlined />, onClick: openWatermarkRemovalPage }, { label: "去除水印", tone: "watermark", icon: <ClearOutlined />, onClick: openWatermarkRemovalPage },
{ label: "图片翻译", tone: "translate", icon: <GlobalOutlined />, onClick: openImageTranslatePage }, { 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) => ( ].map((item) => (
<button <button
key={item.label} key={item.label}
type="button" type="button"
className={`ecom-command-quick-card ecom-command-quick-card--${item.tone}`} className={`ecom-command-quick-card ecom-command-quick-card--${item.tone}`}
disabled={item.disabled}
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
item.onClick?.(); if (item.disabled) {
toast.info("更多功能即将上线,敬请期待!");
} else {
item.onClick?.();
}
}} }}
> >
<span aria-hidden="true">{item.icon}</span> <span aria-hidden="true">{item.icon}</span>
@@ -7483,6 +7801,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const quickDetailVisibleSelect = quickDetailBasicSelects.find((item) => item.key === visibleQuickSetSelect) ?? null; const quickDetailVisibleSelect = quickDetailBasicSelects.find((item) => item.key === visibleQuickSetSelect) ?? null;
const quickHotVisibleSelect = quickHotBasicSelects.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 = ( 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+详情页生成"> <main key="quick-detail" className={`ecom-quick-set-page ecom-quick-detail-page ecom-tool-page-enter${isQuickPanelCollapsed ? " is-panel-collapsed" : ""}`} aria-label="A+详情页生成">
@@ -7944,6 +8263,266 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
</main> </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 = ( const detailPreview = (
<main className="product-clone-preview product-clone-preview--detail" aria-label="A+详情预览" onWheel={handlePreviewWheel}> <main className="product-clone-preview product-clone-preview--detail" aria-label="A+详情预览" onWheel={handlePreviewWheel}>
<div className="product-clone-preview__headline"> <div className="product-clone-preview__headline">
@@ -8039,6 +8618,12 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
</main> </main>
); );
const copywritingPreview = (
<div key="copywriting" className="ecom-quick-page-wrap ecom-tool-page-enter">
<EcommerceCopywritingPanel onClose={closeCopywritingPage} />
</div>
);
const activePreview = isSetTool const activePreview = isSetTool
? setPreview ? setPreview
: isDetail : isDetail
@@ -8066,9 +8651,17 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
{hotClonePreview} {hotClonePreview}
</div> </div>
) )
: clonePreview : isQuickSetTool
? (
<div key={`quick-${activeQuickTool}`} className="ecom-quick-page-wrap ecom-tool-page-enter">
{quickSetGenPreview}
</div>
)
: isCopywritingTool
? copywritingPreview
: clonePreview
: placeholderPreview; : 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 isRecordDetailWorkspace = isMainCloneWorkspace && Boolean(activeHistoryRecordId);
const currentResultCount = canvasNodes.reduce((count, node) => count + node.results.length, 0); const currentResultCount = canvasNodes.reduce((count, node) => count + node.results.length, 0);
const activeHistoryRecord = activeHistoryRecordId ? ecommerceHistoryRecords.find((record) => record.id === activeHistoryRecordId) : null; const activeHistoryRecord = activeHistoryRecordId ? ecommerceHistoryRecords.find((record) => record.id === activeHistoryRecordId) : null;
@@ -8104,7 +8697,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
return ( return (
<section <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} data-tool={activeTool}
aria-label={pageLabel} aria-label={pageLabel}
> >
@@ -8125,10 +8718,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
aria-label={`${pageLabel}参数`} aria-label={`${pageLabel}参数`}
aria-hidden={isCloneTool && isCloneSettingsCollapsed ? true : undefined} 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> </aside>
{isCloneTool && !isSmartCutoutTool && !isQuickDetailTool && !isWatermarkTool && !isTranslateTool && !isImageEditTool && !isHotCloneTool ? ( {isCloneTool && !isSmartCutoutTool && !isQuickDetailTool && !isWatermarkTool && !isTranslateTool && !isImageEditTool && !isHotCloneTool && !isQuickSetTool && !isCopywritingTool ? (
<button <button
type="button" type="button"
className="clone-ai-settings-toggle" className="clone-ai-settings-toggle"
@@ -0,0 +1,289 @@
import { useState } from "react";
import {
AppstoreOutlined,
CopyOutlined,
EditOutlined,
FileTextOutlined,
FireOutlined,
GlobalOutlined,
MessageOutlined,
SmileOutlined,
ThunderboltOutlined,
} from "@ant-design/icons";
export type CopywritingType =
| "self-media"
| "universal"
| "original"
| "imitate"
| "wechat"
| "crossborder"
| "emoji"
| "more";
interface CopywritingTypeOption {
key: CopywritingType;
label: string;
icon: React.ReactNode;
description: string;
}
const copywritingTypes: CopywritingTypeOption[] = [
{ key: "self-media", label: "自媒体文案", icon: <MessageOutlined />, description: "小红书/抖音/公众号风格" },
{ key: "universal", label: "万能写作", icon: <EditOutlined />, description: "通用场景长文短句" },
{ key: "original", label: "一键原创", icon: <ThunderboltOutlined />, description: "快速改写去重" },
{ key: "imitate", label: "文案仿写", icon: <CopyOutlined />, description: "参照爆款风格重写" },
{ key: "wechat", label: "微信营销文案", icon: <FileTextOutlined />, description: "朋友圈/社群转化文案" },
{ key: "crossborder", label: "跨境商品文案", icon: <GlobalOutlined />, description: "Amazon/Temu 卖点描述" },
{ key: "emoji", label: "文案加Emoji", icon: <SmileOutlined />, description: "自动插入表情符号" },
{ key: "more", label: "更多场景", icon: <AppstoreOutlined />, description: "持续更新中" },
];
const wordCountOptions = ["不限", "100字", "300字", "500字", "800字"];
const exampleResults: Record<CopywritingType, Array<{ title: string; body: string; points: string[] }>> = {
"self-media": [
{
title: "超值干发神器,吸水力 MAX",
body: "家人们,我发现了一款干发帽,双层加厚吸水力超强!而且只要个位数就能到手啊!",
points: [
"超强吸水力:这款干发帽采用微纤维材质,轻轻一裹,水分立马被吸走,头发快速告别湿漉漉。",
"柔软亲肤:触感超级柔软,对皮肤和头发都是温柔的抚摸,不会有摩擦伤害哦。",
"加厚设计:比普通干发帽更厚实,吸水效果自然更胜一筹,长发妹子的福音!",
"方便携带:轻巧不占空间,不论是去健身房还是旅行,携带都毫无负担。",
],
},
],
universal: [
{
title: "直接抄作业!科学的减重方法必试!",
body: "姐妹们冲鸭!有很多科学有效的方式可以帮助我们实现理想体重,今天就来分享一下必试的方法!",
points: [
"快乐有氧运动:科学的减重方式,通过有氧运动如慢跑、游泳等,能够促进脂肪燃烧,让身体更健康!",
"均衡饮食规划:摄入足够的蛋白质、蔬果以及谷物,避免过多的高糖和高脂食物,帮助达到减重目标!",
"科学计算热量:了解自己每日所需的卡路里摄入量,合理安排每餐的热量搭配,控制总摄入量。",
"坚持低强度运动:逐渐增加日常活动量,如步行、瑜伽等,通过持续的轻度运动,加速代谢!",
"合理休息调节:不要忽视睡眠的重要性,保证每晚充足的睡眠时间,帮助恢复体力和新陈代谢。",
],
},
],
original: [
{
title: "原创种草|这款干发帽真的值得入!",
body: "洗完头最烦的就是湿哒哒滴水?试试这条双层加厚干发帽,吸水速度真的惊艳到我。",
points: [
"加厚材质,吸水更快更彻底",
"柔软不勒头,长发短发都能用",
"轻便好收纳,差旅党必备",
"性价比超高,入手不亏",
],
},
],
imitate: [
{
title: "仿写爆款|让头发速干的小心机",
body: "姐妹们有没有发现,最近超火的干发帽真的太好用了!轻轻一裹,几分钟头发就半干了。",
points: [
"双层加厚,吸水力翻倍",
"柔软亲肤,不伤发质",
"小巧便携,出门也能带",
"颜值在线,多色可选",
],
},
],
wechat: [
{
title: "朋友圈文案|个位数到手的干发神器",
body: "今天必须给大家安利这个干发帽!双层加厚,吸水超强,个位数就能到手,真的不冲吗?",
points: [
"微纤维材质,轻柔速干",
"加厚设计,吸水更彻底",
"小巧便携,旅行出差都能带",
"限时好价,手慢无",
],
},
],
crossborder: [
{
title: "Amazon ListingSuper Absorbent Hair Turban",
body: "Made with ultra-soft microfiber, this double-layer hair turban dries hair quickly while protecting delicate strands.",
points: [
"Double-layer microfiber for maximum absorbency",
"Gentle on hair and skin, no frizz or breakage",
"Lightweight and travel-friendly design",
"Secure button closure stays in place",
],
},
],
emoji: [
{
title: "✨个位数到手的干发神器,吸水力 MAX!",
body: "家人们👋,我发现了一款超棒的干发帽💧,双层加厚吸水力超强!而且只要个位数就能到手啊🛒!",
points: [
"💦 超强吸水力:微纤维材质,轻轻一裹水分吸走",
"☁️ 柔软亲肤:触感温柔,不伤头发和皮肤",
"🎒 方便携带:轻巧不占空间,旅行健身都能带",
"💰 超值价格:个位数到手,性价比拉满",
],
},
],
more: [
{
title: "更多场景示例",
body: "选择左侧具体文案类型即可生成对应场景内容,更多场景持续更新中。",
points: ["选择合适的文案类型", "填写内容需求", "选择生成字数", "点击开始生成"],
},
],
};
export interface EcommerceCopywritingPanelProps {
onClose: () => void;
}
export default function EcommerceCopywritingPanel({ onClose }: EcommerceCopywritingPanelProps) {
const [selectedType, setSelectedType] = useState<CopywritingType>("self-media");
const [requirement, setRequirement] = useState("");
const [wordCount, setWordCount] = useState("不限");
const [loading, setLoading] = useState(false);
const [results, setResults] = useState<typeof exampleResults["self-media"]>([]);
const handleGenerate = () => {
setLoading(true);
setResults([]);
// 模拟生成延迟
window.setTimeout(() => {
setResults(exampleResults[selectedType]);
setLoading(false);
}, 1200);
};
const selectedTypeLabel = copywritingTypes.find((item) => item.key === selectedType)?.label ?? "文案";
return (
<main className="ecom-copywriting-page ecom-tool-page-enter" aria-label="一键文案">
<div className="ecom-copywriting-body">
<aside className="ecom-copywriting-panel" aria-label="文案设置">
<header className="ecom-copywriting-panel-head">
<strong className="ecom-copywriting-page-title"></strong>
<button type="button" className="ecom-copywriting-back" onClick={onClose}>
</button>
<button type="button" className="ecom-copywriting-back" onClick={onClose}>
</button>
</header>
<section className="ecom-copywriting-section">
<strong className="ecom-copywriting-section-title"></strong>
<div className="ecom-copywriting-type-grid">
{copywritingTypes.map((item) => (
<button
key={item.key}
type="button"
className={`ecom-copywriting-type-card${selectedType === item.key ? " is-active" : ""}`}
onClick={() => setSelectedType(item.key)}
>
<span className="ecom-copywriting-type-icon" aria-hidden="true">
{item.icon}
</span>
<span className="ecom-copywriting-type-label">{item.label}</span>
<span className="ecom-copywriting-type-desc">{item.description}</span>
</button>
))}
</div>
</section>
<section className="ecom-copywriting-section">
<strong className="ecom-copywriting-section-title"></strong>
<textarea
className="ecom-copywriting-textarea"
value={requirement}
onChange={(event) => setRequirement(event.target.value)}
placeholder="例如:主题、核心卖点、适用人群、期望场景等"
rows={5}
/>
</section>
<section className="ecom-copywriting-section">
<strong className="ecom-copywriting-section-title"></strong>
<div className="ecom-copywriting-wordcount">
{wordCountOptions.map((item) => (
<button
key={item}
type="button"
className={wordCount === item ? "is-active" : ""}
onClick={() => setWordCount(item)}
>
{item}
</button>
))}
</div>
</section>
<button
type="button"
className="ecom-copywriting-generate"
onClick={handleGenerate}
disabled={loading}
>
{loading ? (
<>
<span className="ecom-copywriting-spinner" />
</>
) : (
<>
<ThunderboltOutlined />
</>
)}
</button>
</aside>
<section className="ecom-copywriting-stage" aria-label="生成文案预览">
<header className="ecom-copywriting-preview-head">
<h1></h1>
<p>
<span>{selectedTypeLabel}</span> AI
</p>
</header>
<div className="ecom-copywriting-results">
{results.length === 0 && !loading ? (
<div className="ecom-copywriting-empty">
<FileTextOutlined />
<strong></strong>
<em></em>
</div>
) : null}
{loading ? (
<div className="ecom-copywriting-loading">
<span className="ecom-copywriting-spinner" />
<span>AI </span>
</div>
) : null}
{results.map((item, index) => (
<article key={index} className="ecom-copywriting-result-card">
<header>
<span> {index + 1}</span>
<strong>{item.title}</strong>
</header>
<p className="ecom-copywriting-result-body">{item.body}</p>
<ul className="ecom-copywriting-result-points">
{item.points.map((point, pointIndex) => (
<li key={pointIndex}>
<span>{pointIndex + 1}</span>
{point}
</li>
))}
</ul>
</article>
))}
</div>
</section>
</div>
</main>
);
}
File diff suppressed because it is too large Load Diff
+460 -30
View File
@@ -3047,33 +3047,47 @@
border-radius: 18px; border-radius: 18px;
background: #1b1d23; background: #1b1d23;
padding: 12px; padding: 12px;
transition:
border-color 180ms ease,
box-shadow 180ms ease,
transform 180ms ease;
}
.product-clone-page[data-tool="clone"] .clone-ai-input-wrapper:focus-within {
border-color: rgba(0, 255, 136, 0.45);
box-shadow:
0 0 0 1px rgba(0, 255, 136, 0.08),
0 18px 46px rgba(0, 0, 0, 0.32);
transform: translateY(-1px);
} }
.product-clone-page[data-tool="clone"] .clone-ai-send-button { .product-clone-page[data-tool="clone"] .clone-ai-send-button {
display: grid; display: inline-flex;
align-items: center;
justify-content: center;
width: 38px; width: 38px;
height: 38px; height: 38px;
place-items: center;
border: 1px solid #303540; border: 1px solid #303540;
border-radius: 999px; border-radius: 12px;
background: #22252d; background: #00ff88;
color: #eef2f6; color: #06130d;
font-size: 20px;
font-weight: 900;
cursor: pointer; cursor: pointer;
transition: transition:
background-color 160ms ease, background-color 160ms ease,
border-color 160ms ease, border-color 160ms ease,
transform 160ms ease; transform 160ms ease,
box-shadow 160ms ease;
} }
.product-clone-page[data-tool="clone"] .clone-ai-send-button:hover:not(:disabled) { .product-clone-page[data-tool="clone"] .clone-ai-send-button:hover:not(:disabled) {
border-color: #00ff88; border-color: #00ff88;
background: #202c28; background: #00ff88;
box-shadow: 0 10px 24px rgba(0, 255, 136, 0.24);
transform: translateY(-1px);
} }
.product-clone-page[data-tool="clone"] .clone-ai-send-button:active:not(:disabled) { .product-clone-page[data-tool="clone"] .clone-ai-send-button:active:not(:disabled) {
transform: scale(0.94); transform: translateY(0) scale(0.96);
} }
.product-clone-page[data-tool="clone"] .clone-ai-input-wrapper textarea { .product-clone-page[data-tool="clone"] .clone-ai-input-wrapper textarea {
@@ -3082,7 +3096,7 @@
max-height: 183px; max-height: 183px;
border: 0; border: 0;
outline: none; outline: none;
resize: vertical; resize: none;
background: transparent; background: transparent;
color: #eef2f6; color: #eef2f6;
padding: 10px 0 8px; padding: 10px 0 8px;
@@ -3093,17 +3107,15 @@
.product-clone-page[data-tool="clone"] .clone-ai-input-wrapper textarea::placeholder { .product-clone-page[data-tool="clone"] .clone-ai-input-wrapper textarea::placeholder {
color: #687184; color: #687184;
} font-size: 13px;
font-weight: 400;
.product-clone-page[data-tool="clone"] .clone-ai-send-button {
background: #00ff88;
color: #06130d;
} }
.product-clone-page[data-tool="clone"] .clone-ai-send-button:disabled { .product-clone-page[data-tool="clone"] .clone-ai-send-button:disabled {
background: #26342f; background: #26342f;
color: #677569; color: #677569;
cursor: not-allowed; cursor: not-allowed;
opacity: 0.7;
} }
.product-clone-page[data-tool="clone"] .clone-ai-char-count { .product-clone-page[data-tool="clone"] .clone-ai-char-count {
@@ -8945,17 +8957,28 @@
rgba(20, 24, 23, 0.92); rgba(20, 24, 23, 0.92);
box-shadow: var(--ecm-shadow-panel); box-shadow: var(--ecm-shadow-panel);
backdrop-filter: blur(18px); backdrop-filter: blur(18px);
transition:
border-color 180ms ease,
box-shadow 180ms ease,
transform 180ms ease;
} }
.product-clone-page[data-tool="clone"] .clone-ai-input-wrapper:focus-within { .product-clone-page[data-tool="clone"] .clone-ai-input-wrapper:focus-within {
border-color: rgba(var(--ecm-accent-rgb), 0.42); border-color: rgba(var(--ecm-accent-rgb), 0.5);
box-shadow: box-shadow:
0 20px 58px rgba(0, 0, 0, 0.34), 0 22px 62px rgba(0, 0, 0, 0.36),
0 0 0 1px rgba(var(--ecm-accent-rgb), 0.08); 0 0 0 1px rgba(var(--ecm-accent-rgb), 0.1);
transform: translateY(-1px);
} }
.product-clone-page[data-tool="clone"] .clone-ai-input-wrapper textarea { .product-clone-page[data-tool="clone"] .clone-ai-input-wrapper textarea {
color: var(--ecm-text); color: var(--ecm-text);
resize: none;
}
.product-clone-page[data-tool="clone"] .clone-ai-input-wrapper textarea::placeholder {
font-size: 13px;
font-weight: 400;
} }
.product-clone-page[data-tool="clone"] .clone-ai-char-count { .product-clone-page[data-tool="clone"] .clone-ai-char-count {
@@ -9845,10 +9868,14 @@
.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-send { .ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-send {
position: static; position: static;
display: inline-flex;
align-items: center;
justify-content: center;
width: 46px; width: 46px;
height: 46px; height: 46px;
min-height: 46px; min-height: 46px;
border: 1px solid rgba(var(--ecm-accent-rgb), 0.55); border: 1px solid rgba(var(--ecm-accent-rgb), 0.55);
border-radius: 12px;
color: #021b2e; color: #021b2e;
background: linear-gradient(135deg, #16c8df, #18a7ff); background: linear-gradient(135deg, #16c8df, #18a7ff);
box-shadow: 0 12px 28px rgba(var(--ecm-accent-rgb), 0.32); box-shadow: 0 12px 28px rgba(var(--ecm-accent-rgb), 0.32);
@@ -9856,8 +9883,12 @@
} }
.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-send:hover:not(:disabled) { .ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-send:hover:not(:disabled) {
transform: translateY(-1px); transform: translateY(-2px) scale(1.02);
box-shadow: 0 16px 34px rgba(var(--ecm-accent-rgb), 0.36); box-shadow: 0 18px 38px rgba(var(--ecm-accent-rgb), 0.38);
}
.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-send:active:not(:disabled) {
transform: translateY(0) scale(0.98);
} }
.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-send:disabled { .ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-send:disabled {
@@ -9865,6 +9896,7 @@
color: rgba(255, 255, 255, 0.26); color: rgba(255, 255, 255, 0.26);
background: rgba(126, 235, 255, 0.08); background: rgba(126, 235, 255, 0.08);
box-shadow: none; box-shadow: none;
opacity: 0.7;
} }
.ecommerce-standalone .product-clone-page[data-tool="clone"] .clone-ai-char-count { .ecommerce-standalone .product-clone-page[data-tool="clone"] .clone-ai-char-count {
@@ -11053,16 +11085,17 @@
} }
.ecommerce-standalone .product-clone-page[data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer:focus-within { .ecommerce-standalone .product-clone-page[data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer:focus-within {
border-color: var(--ecom-entry-line-strong) !important; border-color: rgba(30, 189, 219, 0.5) !important;
box-shadow: box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.92), inset 0 1px 0 rgba(255, 255, 255, 0.96),
0 0 0 1px rgba(30, 189, 219, 0.08),
var(--ecom-entry-focus), var(--ecom-entry-focus),
var(--ecom-entry-shadow-strong) !important; var(--ecom-entry-shadow-strong) !important;
transform: translateY(-1px); transform: translateY(-1px);
} }
.ecommerce-standalone .product-clone-page[data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer:hover { .ecommerce-standalone .product-clone-page[data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer:hover {
border-color: rgba(30, 189, 219, 0.3) !important; border-color: rgba(30, 189, 219, 0.32) !important;
box-shadow: box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.92), inset 0 1px 0 rgba(255, 255, 255, 0.92),
0 26px 72px rgba(16, 115, 204, 0.13) !important; 0 26px 72px rgba(16, 115, 204, 0.13) !important;
@@ -11070,10 +11103,10 @@
.ecommerce-standalone .product-clone-page[data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer.is-dragging, .ecommerce-standalone .product-clone-page[data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer.is-dragging,
.ecommerce-standalone .product-clone-page[data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer.has-files { .ecommerce-standalone .product-clone-page[data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer.has-files {
border-color: rgba(30, 189, 219, 0.42) !important; border-color: rgba(30, 189, 219, 0.5) !important;
background: background:
linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(244, 253, 255, 0.96) 100%), linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(244, 253, 255, 0.96) 100%),
linear-gradient(135deg, rgba(30, 189, 219, 0.1), rgba(16, 115, 204, 0.06)) !important; linear-gradient(135deg, rgba(30, 189, 219, 0.12), rgba(16, 115, 204, 0.08)) !important;
} }
.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-reference { .ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-reference {
@@ -11114,8 +11147,11 @@
caret-color: var(--ecom-entry-accent) !important; caret-color: var(--ecom-entry-accent) !important;
} }
.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-input::placeholder { .ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-input::placeholder,
color: rgba(16, 32, 44, 0.38) !important; .ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-composer textarea::placeholder {
color: rgba(16, 32, 44, 0.42) !important;
font-size: 13px !important;
font-weight: 400 !important;
} }
.ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-toolbar { .ecommerce-standalone .product-clone-page[data-tool="clone"] .ecom-command-toolbar {
@@ -11200,11 +11236,16 @@
box-shadow: box-shadow:
0 20px 38px rgba(30, 189, 219, 0.34), 0 20px 38px rgba(30, 189, 219, 0.34),
inset 0 1px 0 rgba(255, 255, 255, 0.42) !important; inset 0 1px 0 rgba(255, 255, 255, 0.42) !important;
transform: translateY(-1px); transform: translateY(-2px) scale(1.03);
} }
.ecommerce-standalone .product-clone-page[data-tool="clone"] .clone-ai-send-button.ecom-command-send:active:not(:disabled) { .ecommerce-standalone .product-clone-page[data-tool="clone"] .clone-ai-send-button.ecom-command-send:active:not(:disabled) {
transform: translateY(1px) scale(0.98); transform: translateY(0) scale(0.97);
}
.ecommerce-standalone .product-clone-page[data-tool="clone"] .clone-ai-send-button.ecom-command-send:disabled {
opacity: 0.5 !important;
filter: grayscale(0.35) !important;
} }
.ecommerce-standalone .product-clone-page[data-tool="clone"] .clone-ai-char-count { .ecommerce-standalone .product-clone-page[data-tool="clone"] .clone-ai-char-count {
@@ -12209,3 +12250,392 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d
margin: 0 !important; margin: 0 !important;
font-size: 24px !important; font-size: 24px !important;
} }
/* Final composer toolbelt overrides: keep upload/assets/mode/AI writing responsive and single-line. */
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-toolbar {
flex-direction: row !important;
align-items: center !important;
justify-content: space-between !important;
gap: 12px !important;
min-width: 0 !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-actions {
display: flex !important;
flex: 1 1 auto !important;
flex-wrap: nowrap !important;
align-items: center !important;
gap: 8px !important;
min-width: 0 !important;
overflow-x: auto !important;
overflow-y: visible !important;
padding: 2px !important;
scrollbar-width: none !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-actions::-webkit-scrollbar {
display: none !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-tool {
position: relative !important;
flex: 0 0 auto !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
height: 40px !important;
min-height: 40px !important;
border: 1px solid rgba(16, 32, 44, 0.08) !important;
border-radius: 999px !important;
color: rgba(16, 32, 44, 0.72) !important;
background: rgba(255, 255, 255, 0.82) !important;
box-shadow: 0 6px 18px rgba(16, 115, 204, 0.045), inset 0 1px 0 rgba(255, 255, 255, 0.96) !important;
cursor: pointer !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-tool:hover,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-tool.is-active,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-tool.has-images,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-tool.is-dragging {
border-color: rgba(30, 189, 219, 0.32) !important;
color: #0f829b !important;
background: rgba(232, 249, 253, 0.95) !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-tool--upload {
gap: 7px !important;
width: auto !important;
min-width: max-content !important;
padding: 0 13px !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-tool--icon {
width: 40px !important;
min-width: 40px !important;
padding: 0 !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-tool--icon::after {
position: absolute !important;
left: 50% !important;
bottom: calc(100% + 10px) !important;
z-index: 260 !important;
padding: 6px 9px !important;
border-radius: 8px !important;
color: #ffffff !important;
background: rgba(16, 32, 44, 0.72) !important;
content: attr(data-tooltip) !important;
font-size: 12px !important;
font-weight: 760 !important;
line-height: 1 !important;
opacity: 0 !important;
pointer-events: none !important;
transform: translate(-50%, 4px) !important;
white-space: nowrap !important;
transition: opacity 150ms ease, transform 150ms ease !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-tool--icon:hover::after,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-tool--icon:focus-visible::after {
opacity: 1 !important;
transform: translate(-50%, 0) !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--library,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--work-mode,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--ai-write {
gap: 14px !important;
border-color: rgba(16, 32, 44, 0.08) !important;
border-radius: 24px !important;
background: rgba(255, 255, 255, 0.98) !important;
box-shadow: 0 30px 76px rgba(16, 115, 204, 0.16), inset 0 1px 0 rgba(255, 255, 255, 0.92) !important;
color: #10202c !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover--library {
width: min(540px, calc(100% - var(--composer-popover-left, 0px))) !important;
max-width: min(540px, calc(100% - var(--composer-popover-left, 0px))) !important;
min-height: 300px !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover--work-mode {
width: min(340px, calc(100% - var(--composer-popover-left, 0px))) !important;
max-width: min(340px, calc(100% - var(--composer-popover-left, 0px))) !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover--ai-write {
width: min(430px, calc(100% - var(--composer-popover-left, 0px))) !important;
max-width: min(430px, calc(100% - var(--composer-popover-left, 0px))) !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-head,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--work-mode header,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--ai-write header {
display: flex !important;
align-items: center !important;
justify-content: space-between !important;
gap: 12px !important;
color: #10202c !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-head strong,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--work-mode header strong,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--ai-write header strong {
color: #10202c !important;
font-size: 22px !important;
font-weight: 880 !important;
white-space: nowrap !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-help {
display: inline-flex !important;
align-items: center !important;
gap: 5px !important;
min-height: 28px !important;
padding: 0 8px !important;
border: 0 !important;
color: #1073cc !important;
background: transparent !important;
box-shadow: none !important;
font-size: 13px !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-tabs {
display: flex !important;
flex-wrap: nowrap !important;
gap: 8px !important;
overflow-x: auto !important;
scrollbar-width: none !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-tabs::-webkit-scrollbar {
display: none !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-tabs button {
flex: 0 0 auto !important;
min-height: 40px !important;
padding: 0 14px !important;
border: 0 !important;
border-radius: 10px !important;
color: #687885 !important;
background: #f2f5f8 !important;
box-shadow: none !important;
font-size: 14px !important;
text-align: center !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-tabs button.is-active {
color: #10202c !important;
background: #e8edf3 !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-empty {
display: grid !important;
min-height: 170px !important;
place-items: center !important;
align-content: center !important;
gap: 8px !important;
color: #8b99a4 !important;
text-align: center !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-empty .anticon {
color: rgba(30, 189, 219, 0.58) !important;
font-size: 42px !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-empty strong {
color: #7d8a94 !important;
font-size: 15px !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-empty span {
color: #a1adb6 !important;
font-size: 12px !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-list {
display: grid !important;
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
gap: 10px !important;
min-width: 0 !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-list button,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--work-mode > button {
display: grid !important;
align-content: center !important;
gap: 5px !important;
min-height: 72px !important;
border-color: rgba(16, 32, 44, 0.08) !important;
border-radius: 16px !important;
color: #10202c !important;
background: linear-gradient(180deg, #ffffff, #f7fbfc) !important;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.86) !important;
white-space: normal !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-list button:hover,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-list button.is-active,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--work-mode > button:hover,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--work-mode > button.is-active {
border-color: rgba(30, 189, 219, 0.34) !important;
background: linear-gradient(180deg, #fafdff, #eefbfe) !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-list button strong,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--work-mode > button strong {
min-width: 0 !important;
overflow: hidden !important;
color: #10202c !important;
font-size: 14px !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-list button span,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--work-mode > button span,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--work-mode header span,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--ai-write header span {
color: #7a8c98 !important;
font-size: 12px !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--ai-write textarea {
min-height: 136px !important;
width: 100% !important;
padding: 14px !important;
border: 1px solid rgba(16, 32, 44, 0.08) !important;
border-radius: 16px !important;
color: #10202c !important;
background: #fbfdfe !important;
box-shadow: none !important;
font-size: 14px !important;
line-height: 1.6 !important;
resize: none !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-ai-submit {
min-height: 44px !important;
justify-content: center !important;
border: 0 !important;
border-radius: 14px !important;
color: #ffffff !important;
background: #181b1f !important;
box-shadow: 0 12px 28px rgba(16, 32, 44, 0.14) !important;
text-align: center !important;
}
@media (max-width: 640px) {
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-toolbar {
flex-direction: row !important;
align-items: center !important;
min-height: 56px !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-actions {
flex: 1 1 auto !important;
max-width: calc(100% - 54px) !important;
gap: 7px !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-tool {
height: 38px !important;
min-height: 38px !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-tool--icon {
width: 38px !important;
min-width: 38px !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-tool--upload {
max-width: 112px !important;
padding: 0 11px !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-tool--upload strong {
max-width: 56px !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-tool--icon::after {
display: none !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover--library,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover--work-mode,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover--ai-write {
inset: calc(100% + 12px) auto auto 0 !important;
left: 0 !important;
right: auto !important;
width: 100% !important;
min-width: 0 !important;
max-width: 100% !important;
max-height: min(420px, calc(100dvh - 380px)) !important;
overflow-x: hidden !important;
overflow-y: auto !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-library-list {
grid-template-columns: minmax(0, 1fr) !important;
}
}
@media (max-width: 390px) {
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-tool--upload {
min-width: 40px !important;
width: 40px !important;
padding: 0 !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-tool--upload strong {
position: absolute !important;
width: 1px !important;
height: 1px !important;
overflow: hidden !important;
clip: rect(0 0 0 0) !important;
clip-path: inset(50%) !important;
}
}
@media (min-width: 641px) {
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover.ecom-command-popover--library {
width: min(540px, calc(100% - var(--composer-popover-left, 0px))) !important;
max-width: min(540px, calc(100% - var(--composer-popover-left, 0px))) !important;
min-height: 300px !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover.ecom-command-popover--work-mode {
width: min(340px, calc(100% - var(--composer-popover-left, 0px))) !important;
max-width: min(340px, calc(100% - var(--composer-popover-left, 0px))) !important;
}
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover.ecom-command-popover--ai-write {
width: min(430px, calc(100% - var(--composer-popover-left, 0px))) !important;
max-width: min(430px, calc(100% - var(--composer-popover-left, 0px))) !important;
}
}
@media (max-width: 640px) {
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover.ecom-command-popover--library,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover.ecom-command-popover--work-mode,
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover.ecom-command-popover--ai-write {
inset: calc(100% + 12px) auto auto 0 !important;
left: 0 !important;
right: auto !important;
width: 100% !important;
min-width: 0 !important;
max-width: 100% !important;
}
}
@media (max-height: 700px) {
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover.ecom-command-popover--library {
min-height: 0 !important;
max-height: min(300px, calc(100dvh - 330px)) !important;
overflow-y: auto !important;
}
}
+4 -3
View File
@@ -1,4 +1,4 @@
@media (max-width: 640px) { @media (max-width: 640px) {
html body .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover.ecom-command-popover { html body .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover.ecom-command-popover {
position: absolute !important; position: absolute !important;
@@ -3473,8 +3473,8 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d
@media (max-width: 420px) { @media (max-width: 420px) {
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-option-row.ecom-command-option-row--settings { html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-option-row.ecom-command-option-row--settings {
display: grid !important; display: flex !important;
grid-template-columns: repeat(4, minmax(0, 1fr)) !important; flex-wrap: nowrap !important;
gap: 7px !important; gap: 7px !important;
justify-content: stretch !important; justify-content: stretch !important;
overflow: visible !important; overflow: visible !important;
@@ -3482,6 +3482,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-option-row.ecom-command-option-row--settings button { html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-option-row.ecom-command-option-row--settings button {
display: inline-flex !important; display: inline-flex !important;
flex: 1 1 0 !important;
width: auto !important; width: auto !important;
min-width: 0 !important; min-width: 0 !important;
max-width: none !important; max-width: none !important;