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
This commit is contained in:
@@ -32,6 +32,7 @@ import {
|
||||
Gift,
|
||||
MagicWand,
|
||||
Mountains,
|
||||
PaperPlaneRight,
|
||||
ShoppingBag,
|
||||
User,
|
||||
VideoCamera,
|
||||
@@ -289,7 +290,9 @@ type CloneModelPanelTab = "scene" | "model";
|
||||
type CloneVideoQualityKey = "standard" | "high" | "ultra";
|
||||
type ProductSetStatus = "idle" | "ready" | "generating" | "done" | "failed";
|
||||
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 CloneModelSelectKey = "gender" | "age" | "ethnicity" | "body";
|
||||
type CloneReferenceMode = "upload" | "link";
|
||||
@@ -1062,11 +1065,13 @@ const commerceScenarioOptions: Array<{ key: CommerceScenarioKey; label: string;
|
||||
{ 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: "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: "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 scenarioSettingsKeys: CommerceScenarioKey[] = ["poster", "mainImage", "model", "scene", "festival", "salesVideo"];
|
||||
const scenarioAdvancedSettingsKeys: CommerceScenarioKey[] = ["model", "salesVideo"];
|
||||
const commerceScenarioOutputMap: Record<Exclude<CommerceScenarioKey, "popular">, ProductSetOutputKey> = {
|
||||
poster: "set",
|
||||
mainImage: "set",
|
||||
@@ -1937,6 +1942,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
const [isComposerMenuClosing, setIsComposerMenuClosing] = useState(false);
|
||||
const [composerPopoverLeft, setComposerPopoverLeft] = 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 [inspirationPreview, setInspirationPreview] = useState<{ mediaUrl: string; mediaType: "image" | "video"; prompt: string } | null>(null);
|
||||
const [isQuickPanelCollapsed, setIsQuickPanelCollapsed] = useState(false);
|
||||
@@ -2400,6 +2409,18 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
[cloneOutput, platform],
|
||||
);
|
||||
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(
|
||||
() => getPlatformLanguageOptions(productSetPlatform, productSetMarket),
|
||||
[productSetMarket, productSetPlatform],
|
||||
@@ -2445,6 +2466,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
: activeCommerceScenario === "popular"
|
||||
? popularCommerceScenarioTemplates
|
||||
: commerceScenarioTemplates.filter((template) => template.scenario === activeCommerceScenario);
|
||||
const shouldShowScenarioSettings = activeCommerceScenario !== null && scenarioSettingsKeys.includes(activeCommerceScenario);
|
||||
useEffect(() => {
|
||||
templateStripRef.current?.scrollTo({ left: 0, behavior: "auto" });
|
||||
}, [activeCommerceScenario, isCloneTemplateStripVisible]);
|
||||
@@ -3844,7 +3866,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
|
||||
const handleCommerceScenarioClick = (nextScenario: CommerceScenarioKey) => {
|
||||
if (nextScenario === activeCommerceScenario) {
|
||||
setIsCloneTemplateStripVisible((visible) => !visible);
|
||||
setActiveCommerceScenario(null);
|
||||
setIsCloneTemplateStripVisible(false);
|
||||
setComposerMenu(null);
|
||||
return;
|
||||
}
|
||||
@@ -5803,6 +5826,96 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
? 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 composerLanguageOptions = Array.from(new Set(marketLanguageOptions.flatMap((option) => option.languages))).map((item) => ({
|
||||
language: item,
|
||||
@@ -5816,6 +5929,37 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
if (!menuToRender) return null;
|
||||
const popoverClosingClass = !composerMenu && isComposerMenuClosing ? " is-closing" : "";
|
||||
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") {
|
||||
return (
|
||||
<div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--grid ecom-command-popover--mode${popoverClosingClass}`} style={composerPopoverStyle}>
|
||||
@@ -5846,8 +5990,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
<div key={composerPopoverKey} className={`ecom-command-popover ecom-command-popover--languages${popoverClosingClass}`} style={composerPopoverStyle}>
|
||||
{composerLanguageOptions.map((option) => (
|
||||
<button key={option.language} type="button" className={language === option.language ? "is-active" : ""} onClick={() => { setLanguage(option.language); setComposerMenu(null); }}>
|
||||
<strong>{option.language}</strong>
|
||||
<span>({option.countries.slice(0, 3).join(" / ")}{option.countries.length > 3 ? " +" + String(option.countries.length - 3) : ""})</span>
|
||||
{option.language}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -5856,17 +5999,11 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
if (menuToRender === "ratio") {
|
||||
return (
|
||||
<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);
|
||||
return (
|
||||
<button key={option} type="button" className={ratio === option ? "is-active" : ""} onClick={() => { setRatio(option); setComposerMenu(null); }}>
|
||||
<span className="ecom-command-ratio-icon" aria-hidden="true">
|
||||
<TableOutlined />
|
||||
</span>
|
||||
<span className="ecom-command-ratio-copy">
|
||||
<strong>{ratioParts.size}</strong>
|
||||
<em>{ratioParts.aspect}</em>
|
||||
</span>
|
||||
{ratioParts.aspect}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -6460,24 +6597,28 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="ecom-command-option-row ecom-command-option-row--settings">
|
||||
<button type="button" className={composerMenu === "platform" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("platform", event)}>
|
||||
<span className="ecom-command-option-icon" aria-hidden="true"><GlobalOutlined /></span>
|
||||
<span>平台</span>{platform}
|
||||
</button>
|
||||
<button type="button" className={composerMenu === "language" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("language", event)}>
|
||||
<span className="ecom-command-option-icon" aria-hidden="true"><FileImageOutlined /></span>
|
||||
<span>语种</span>{language}
|
||||
</button>
|
||||
<button type="button" className={composerMenu === "ratio" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("ratio", event)}>
|
||||
<span className="ecom-command-option-icon" aria-hidden="true"><TableOutlined /></span>
|
||||
<span>比例</span>{formatRatioDisplayValue(ratio)}
|
||||
</button>
|
||||
<button type="button" className={composerMenu === "settings" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("settings", event)}>
|
||||
<span className="ecom-command-option-icon" aria-hidden="true"><SettingOutlined /></span>
|
||||
<span>设置</span>{composerSettingLabel}
|
||||
</button>
|
||||
</div>
|
||||
{shouldShowScenarioSettings ? (
|
||||
<div className="ecom-command-option-row ecom-command-option-row--settings">
|
||||
<button type="button" className={composerMenu === "platform" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("platform", event)}>
|
||||
<span className="ecom-command-option-icon" aria-hidden="true"><GlobalOutlined /></span>
|
||||
<span>平台</span>{platform}
|
||||
</button>
|
||||
<button type="button" className={composerMenu === "language" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("language", event)}>
|
||||
<span className="ecom-command-option-icon" aria-hidden="true"><FileImageOutlined /></span>
|
||||
<span>语种</span>{language}
|
||||
</button>
|
||||
<button type="button" className={composerMenu === "ratio" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("ratio", event)}>
|
||||
<span className="ecom-command-option-icon" aria-hidden="true"><TableOutlined /></span>
|
||||
<span>比例</span>{getRatioDisplayParts(ratio).aspect}
|
||||
</button>
|
||||
{scenarioAdvancedSettingsKeys.includes(activeCommerceScenario) ? (
|
||||
<button type="button" className={composerMenu === "settings" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("settings", event)}>
|
||||
<span className="ecom-command-option-icon" aria-hidden="true"><SettingOutlined /></span>
|
||||
<span>设置</span>{composerSettingLabel}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
<textarea
|
||||
ref={requirementTextareaRef}
|
||||
value={requirement}
|
||||
@@ -6501,7 +6642,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
<div className="ecom-command-composer-actions">
|
||||
<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()}
|
||||
onDragEnter={(event) => {
|
||||
event.preventDefault();
|
||||
@@ -6515,18 +6656,71 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
const files = Array.from(event.dataTransfer.files);
|
||||
if (files.length) addComposerAssets(files);
|
||||
}}
|
||||
aria-label="上传素材"
|
||||
title="上传素材"
|
||||
>
|
||||
<span aria-hidden="true"><PaperClipOutlined /></span>
|
||||
<strong>上传素材</strong>
|
||||
</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 className="ecom-command-submit-row">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
{renderComposerMenu()}
|
||||
{composerTooltip ? createPortal(
|
||||
<span className="ecom-command-tooltip-floating" style={{ left: composerTooltip.left, top: composerTooltip.top }}>
|
||||
{composerTooltip.text}
|
||||
</span>,
|
||||
document.body
|
||||
) : null}
|
||||
</div>
|
||||
{(status === "idle" || status === "ready") && !showMainVideoWorkspace && activeCommerceScenario !== null && isCloneTemplateStripVisible ? (
|
||||
<div className={`ecom-command-template-carousel ecom-command-template-carousel--${activeCommerceScenario}`}>
|
||||
|
||||
Reference in New Issue
Block a user