From 0384d7f2a379c19698978ee10dfa20949a6f3a5d Mon Sep 17 00:00:00 2001 From: OmniAI Developer Date: Mon, 8 Jun 2026 11:39:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BE=A7=E8=BE=B9=E6=A0=8F=E9=A1=BA?= =?UTF-8?q?=E5=BA=8F=E8=B0=83=E6=95=B4=E3=80=81=E6=A8=A1=E5=9E=8B=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=8E=BB=E9=99=A4=E7=A7=AF=E5=88=86=E4=BB=B7=E6=A0=BC?= =?UTF-8?q?=E3=80=81=E4=BF=AE=E5=A4=8Dcanvas.css=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 侧边栏:社区移到底部,工具盒移到资产库上方 - 生成页面:图像/视频模型选择下拉去除积分价格文本 - 修复 canvas.css 多余的右花括号语法错误 --- src/components/AppShell.tsx | 4 +- src/features/ecommerce/EcommercePage.tsx | 37 +++++++-- .../ecommerce/EcommerceVideoWorkspace.tsx | 11 ++- src/features/workbench/workbenchConstants.ts | 14 ++-- src/styles/pages/canvas.css | 1 - src/styles/pages/ecommerce-video.css | 75 ++++++++++++++++--- src/styles/pages/ecommerce.css | 61 +++++++++++++-- src/utils/enterpriseVideoPolicy.ts | 10 +-- 8 files changed, 172 insertions(+), 41 deletions(-) diff --git a/src/components/AppShell.tsx b/src/components/AppShell.tsx index 4bb75c1..b516d34 100644 --- a/src/components/AppShell.tsx +++ b/src/components/AppShell.tsx @@ -102,9 +102,9 @@ function AppShell({ "canvas", "scriptTokens", "tokenUsage", - "community", - "assets", "more", + "assets", + "community", ]; return orderedKeys .map((key) => navItems.find((item) => item.key === key)) diff --git a/src/features/ecommerce/EcommercePage.tsx b/src/features/ecommerce/EcommercePage.tsx index 179a40b..79cf9f2 100644 --- a/src/features/ecommerce/EcommercePage.tsx +++ b/src/features/ecommerce/EcommercePage.tsx @@ -516,6 +516,18 @@ const formatRatioDisplayValue = (value: string) => { const ratio = value.match(/\d+(?:\.\d+)?\s*[::]\s*\d+(?:\.\d+)?/u)?.[0]?.replace(/\s+/g, "").replace(/:/g, ":") ?? ""; return size && ratio ? `${size}\u00a0\u00a0\u00a0${ratio}` : value.replace(/^套图[::]\s*/, ""); }; +/** Extract CSS aspect-ratio from a ratio string like "1000x1000px 1:1" -> "1 / 1" */ +const parseRatioToAspectCss = (ratioStr: string): string => { + const match = ratioStr.match(/(\d+)\s*[::]\s*(\d+)/u); + if (!match) return "1 / 1"; + return `${match[1]} / ${match[2]}`; +}; +/** Normalize ratio display string ("1000×1000px 1:1") to API format ("1:1") */ +const normalizeRatioForApi = (ratioStr: string): string => { + const match = ratioStr.match(/(\d+)\s*[::]\s*(\d+)/u); + if (!match) return "1:1"; + return `${match[1]}:${match[2]}`; +}; const greatestCommonDivisor = (left: number, right: number): number => { let a = Math.abs(left); let b = Math.abs(right); @@ -866,6 +878,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const [videoOutfitVideoFile, setVideoOutfitVideoFile] = useState(null); const [videoOutfitRefFile, setVideoOutfitRefFile] = useState(null); const [isCloneSettingsCollapsed, setIsCloneSettingsCollapsed] = useState(false); + const [previewZoom, setPreviewZoom] = useState(1); const [requirement, setRequirement] = useState(""); const [requirementImageMentionQuery, setRequirementImageMentionQuery] = useState(null); const [cloneSettingName, setCloneSettingName] = useState("新建创作"); @@ -1594,6 +1607,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const stamp = Date.now(); for (const countKey of cloneSetCountOptions.map((o) => o.key)) { + if (imageAbortRef.current.current) break; const count = counts[countKey]; for (let i = 0; i < count; i++) { if (imageAbortRef.current.current) break; @@ -1603,7 +1617,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const { taskId } = await aiGenerationClient.createImageTask({ model: IMAGE_MODEL, prompt: fullPrompt, - ratio: pRatio, + ratio: normalizeRatioForApi(pRatio), quality: pRatio.includes("720") ? "720P" : "1080P", gridMode: "single", referenceUrls, @@ -1640,7 +1654,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { return; } setResultFn(generatedUrls); - setStatusFn(generatedUrls.some(Boolean) ? "done" : "idle"); + setStatusFn(generatedUrls.some(Boolean) ? "done" : "failed"); } catch (err) { if (imageAbortRef.current.current) { setStatusFn("idle"); @@ -1687,7 +1701,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const { taskId } = await aiGenerationClient.createImageTask({ model: IMAGE_MODEL, prompt, - ratio: pRatio, + ratio: normalizeRatioForApi(pRatio), quality: pRatio.includes("720") ? "720P" : "1080P", gridMode: "single", referenceUrls, @@ -2010,7 +2024,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { for (let i = 0; i < count; i++) { clonePreviewCards.push({ id: `${countKey}-${i}`, - src: results[cloneIndex]?.src || productSetPreviewCards[cloneIndex % productSetPreviewCards.length]?.src || "", + src: productSetResultImages[cloneIndex] || productSetPreviewCards[cloneIndex % productSetPreviewCards.length]?.src || "", label: `${info.label}${count > 1 ? ` ${i + 1}` : ""}`, }); cloneIndex++; @@ -2311,6 +2325,11 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { 上传商品图,AI 即刻生成 符合多电商平台规范 的高转化率商品素材。 +
+ + {Math.round(previewZoom * 100)}% + +
{cloneOutput === "video" ? ( @@ -2433,8 +2452,9 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { ) : ( <> {status === "done" ? ( +
- @@ -2442,19 +2462,20 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
{cloneOutput === "set" ? ( clonePreviewCards.map((card) => ( - )) ) : results[0]?.src ? ( - ) : null}
+
) : (
{status === "generating" ? : status === "failed" ? : } @@ -2643,7 +2664,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { ) : null} {isSetTool ? setPreview : isDetail ? detailPreview : isTryOn ? tryOnPreview : isCloneTool ? (cloneOutput === "video" ? ( -
+
).isAuthenticated)} productImageDataUrls={productImages.map((img) => img.src)} diff --git a/src/features/ecommerce/EcommerceVideoWorkspace.tsx b/src/features/ecommerce/EcommerceVideoWorkspace.tsx index 0fd211c..4381253 100644 --- a/src/features/ecommerce/EcommerceVideoWorkspace.tsx +++ b/src/features/ecommerce/EcommerceVideoWorkspace.tsx @@ -119,6 +119,7 @@ export default function EcommerceVideoWorkspace({ const [error, setError] = useState(null); const [actionNotice, setActionNotice] = useState(null); const [previewMedia, setPreviewMedia] = useState<{ url: string; type: "image" | "video" } | null>(null); + const [flowZoom, setFlowZoom] = useState(1); const abortControllerRef = useRef(null); const renderAbortRef = useRef({ current: false }); const setView = useAppStore((s) => s.setView); @@ -600,6 +601,12 @@ export default function EcommerceVideoWorkspace({ })} +
+ + {Math.round(flowZoom * 100)}% + +
+
{onOpenHistory ? (
{/* ── Delivery dock ────────────────────────────── */} {primaryVideo ? ( diff --git a/src/features/workbench/workbenchConstants.ts b/src/features/workbench/workbenchConstants.ts index 73aa2ad..c8d7f9a 100644 --- a/src/features/workbench/workbenchConstants.ts +++ b/src/features/workbench/workbenchConstants.ts @@ -231,13 +231,13 @@ export const MODE_OPTIONS: WorkbenchOption[] = (Object.keys(MODE_META) as Workbe })); export const IMAGE_MODEL_OPTIONS: WorkbenchOption[] = [ - { value: "wan2.7-image-pro", label: "wan 2.7 Pro 4K · 0.20 积分" }, - { value: "wan2.7-image", label: "wan 2.7 · 0.20 积分" }, - { value: "gpt-image-2", label: "GPT-Image-2 · 0.20 积分" }, - { value: "gpt-image-2-vip", label: "GPT-Image-2 VIP · 0.20 积分" }, - { value: "nano-banana-pro", label: "Nano Banana Pro · 0.20 积分" }, - { value: "nano-banana-2", label: "Nano Banana 2 · 0.20 积分" }, - { value: "nano-banana-fast", label: "Nano Banana · 0.20 积分" }, + { value: "wan2.7-image-pro", label: "wan 2.7 Pro 4K" }, + { value: "wan2.7-image", label: "wan 2.7" }, + { value: "gpt-image-2", label: "GPT-Image-2" }, + { value: "gpt-image-2-vip", label: "GPT-Image-2 VIP" }, + { value: "nano-banana-pro", label: "Nano Banana Pro" }, + { value: "nano-banana-2", label: "Nano Banana 2" }, + { value: "nano-banana-fast", label: "Nano Banana" }, ]; export const VIDEO_MODEL_OPTIONS: WorkbenchOption[] = ENTERPRISE_VIDEO_MODEL_OPTIONS.map((option) => ({ ...option })); diff --git a/src/styles/pages/canvas.css b/src/styles/pages/canvas.css index e795046..e6c66cd 100644 --- a/src/styles/pages/canvas.css +++ b/src/styles/pages/canvas.css @@ -947,4 +947,3 @@ height: 100%; cursor: crosshair; } -} diff --git a/src/styles/pages/ecommerce-video.css b/src/styles/pages/ecommerce-video.css index 715671c..72fbf81 100644 --- a/src/styles/pages/ecommerce-video.css +++ b/src/styles/pages/ecommerce-video.css @@ -6,9 +6,13 @@ align-content: initial; justify-items: initial; gap: 0; - overflow: hidden; + overflow: auto; background: #0e1014; - scrollbar-color: #353b45 #0e1014; + scrollbar-width: none; + -ms-overflow-style: none; +} +.product-clone-page[data-tool="clone"] .product-clone-preview--video::-webkit-scrollbar { + display: none; } .ecom-video-workspace { @@ -20,6 +24,11 @@ overflow: hidden; background: #0e1014; color: #e5ebf4; + scrollbar-width: none; + -ms-overflow-style: none; +} +.ecom-video-workspace::-webkit-scrollbar { + display: none; } .ecom-video-flowbar { @@ -112,6 +121,42 @@ gap: 8px; } +/* ── Flowbar zoom controls ─────────────────────────── */ +.ecom-video-flowbar__zoom { + display: inline-flex; + align-items: center; + gap: 5px; +} +.ecom-video-flowbar__zoom button { + width: 26px; + height: 26px; + border: 1px solid #2c3038; + border-radius: 6px; + background: #1a1d24; + color: #8890a0; + font-size: 14px; + font-weight: 700; + cursor: pointer; + display: grid; + place-items: center; + transition: border-color 150ms ease, color 150ms ease; +} +.ecom-video-flowbar__zoom button:hover:not(:disabled) { + border-color: #00ff88; + color: #00ff88; +} +.ecom-video-flowbar__zoom button:disabled { + opacity: 0.35; + cursor: not-allowed; +} +.ecom-video-flowbar__zoom span { + min-width: 38px; + text-align: center; + font-size: 11px; + font-weight: 600; + color: #6a7282; +} + .ecom-video-flowbar__error { max-width: min(260px, 28vw); overflow: hidden; @@ -181,8 +226,13 @@ background: #101318; padding: 32px 40px; display: flex; - align-items: stretch; + align-items: flex-start; justify-content: center; + scrollbar-width: none; + -ms-overflow-style: none; +} +.ecom-video-flow-canvas::-webkit-scrollbar { + display: none; } .ecom-video-flow-map { @@ -418,22 +468,23 @@ width: 38px; height: 38px; place-items: center; - border: 1px solid #3d3020; - border-radius: 8px; - background: #15181f; - color: #ffe1ad; + border: 1px solid #00cc6a; + border-radius: 9px; + background: #00ff88; + color: #06130d; font-size: 16px; + font-weight: 700; cursor: pointer; transition: transform 150ms ease, - border-color 150ms ease, - background-color 150ms ease; + filter 150ms ease, + box-shadow 150ms ease; } .ecom-video-flow-dock button:hover { - border-color: #4d3a1a; - background: #241c12; - transform: translateY(-1px); + filter: brightness(1.08); + box-shadow: 0 2px 12px rgba(0, 255, 136, 0.25); + transform: translateY(-2px); } .ecom-video-flow-notice { diff --git a/src/styles/pages/ecommerce.css b/src/styles/pages/ecommerce.css index af9ae8b..bfeee7d 100644 --- a/src/styles/pages/ecommerce.css +++ b/src/styles/pages/ecommerce.css @@ -2792,12 +2792,18 @@ position: relative; display: grid; min-height: 0; - overflow: hidden; - align-content: center; + overflow-y: auto; + overflow-x: hidden; + align-content: safe center; justify-items: center; gap: 22px; background: #101115; padding: 92px 46px 142px; + scrollbar-width: none; + -ms-overflow-style: none; +} +.product-clone-page[data-tool="clone"] .clone-ai-preview::-webkit-scrollbar { + display: none; } .product-clone-page[data-tool="clone"] .clone-ai-preview-header { @@ -2828,6 +2834,50 @@ color: #00ff88; } +/* ── Preview zoom controls ─────────────────────────── */ +.product-clone-page[data-tool="clone"] .clone-ai-preview-zoom { + display: flex; + align-items: center; + gap: 6px; + margin-top: 8px; +} +.product-clone-page[data-tool="clone"] .clone-ai-preview-zoom button { + width: 28px; + height: 28px; + border: 1px solid #2c3038; + border-radius: 6px; + background: #1b1d23; + color: #a0a8b8; + font-size: 16px; + font-weight: 700; + cursor: pointer; + display: grid; + place-items: center; + transition: border-color 150ms ease, color 150ms ease; +} +.product-clone-page[data-tool="clone"] .clone-ai-preview-zoom button:hover:not(:disabled) { + border-color: #00ff88; + color: #00ff88; +} +.product-clone-page[data-tool="clone"] .clone-ai-preview-zoom button:disabled { + opacity: 0.35; + cursor: not-allowed; +} +.product-clone-page[data-tool="clone"] .clone-ai-preview-zoom span { + min-width: 42px; + text-align: center; + font-size: 12px; + font-weight: 600; + color: #758096; +} +.product-clone-page[data-tool="clone"] .clone-ai-preview-zoom-wrap { + flex-shrink: 0; + display: flex; + align-items: flex-start; + justify-content: center; + min-width: max-content; +} + .product-clone-page[data-tool="clone"] .clone-ai-empty-state { display: grid; width: min(100%, 600px); @@ -2935,7 +2985,8 @@ } .product-clone-page[data-tool="clone"] .clone-ai-main-result { - height: 440px; + width: 100%; + height: auto; } .product-clone-page[data-tool="clone"] .clone-ai-result-grid { @@ -2945,12 +2996,12 @@ } .product-clone-page[data-tool="clone"] .clone-ai-result-grid button { - height: 210px; + width: 100%; + height: auto; } .product-clone-page[data-tool="clone"] .clone-ai-result-grid button:first-child { grid-column: 1 / -1; - height: 240px; } .product-clone-page[data-tool="clone"] .clone-ai-main-result span, diff --git a/src/utils/enterpriseVideoPolicy.ts b/src/utils/enterpriseVideoPolicy.ts index ee73655..2240ea2 100644 --- a/src/utils/enterpriseVideoPolicy.ts +++ b/src/utils/enterpriseVideoPolicy.ts @@ -8,27 +8,27 @@ export const ENTERPRISE_WANXIANG_I2V_MODEL = "wan2.7-i2v"; export const ENTERPRISE_VIDEO_MODEL_OPTIONS = [ { value: HAPPY_HORSE_UI_MODEL, - label: "HappyHorse 1.0 · 0.72 积分/秒起", + label: "HappyHorse 1.0", description: "自动匹配文生视频、首帧图生视频或参考图生视频", }, { value: VIDU_UI_MODEL, - label: "Vidu Q3 Turbo · 0.40 积分/秒起", + label: "Vidu Q3 Turbo", description: "自动匹配文生视频或图生视频,支持16秒", }, { value: PIXVERSE_UI_MODEL, - label: "PixVerse V6 · 0.40 积分/秒起", + label: "PixVerse V6", description: "自动匹配文生视频或图生视频,擅长动作特效", }, { value: ENTERPRISE_WANXIANG_I2V_MODEL, - label: "万相 图生视频 · 0.60 积分/秒起", + label: "万相 图生视频", description: "图生视频模型,支持首帧图驱动", }, { value: ENTERPRISE_KLING_MODEL, - label: "Kling V3 Omni · 0.60 积分/秒起", + label: "Kling V3 Omni", description: "支持文生视频、图生视频及多模态参考生成", }, ] as const;