feat: 侧边栏顺序调整、模型选择去除积分价格、修复canvas.css语法错误

- 侧边栏:社区移到底部,工具盒移到资产库上方
- 生成页面:图像/视频模型选择下拉去除积分价格文本
- 修复 canvas.css 多余的右花括号语法错误
This commit is contained in:
OmniAI Developer
2026-06-08 11:39:28 +08:00
parent f920630160
commit 0384d7f2a3
8 changed files with 172 additions and 41 deletions
+2 -2
View File
@@ -102,9 +102,9 @@ function AppShell({
"canvas", "canvas",
"scriptTokens", "scriptTokens",
"tokenUsage", "tokenUsage",
"community",
"assets",
"more", "more",
"assets",
"community",
]; ];
return orderedKeys return orderedKeys
.map((key) => navItems.find((item) => item.key === key)) .map((key) => navItems.find((item) => item.key === key))
+29 -8
View File
@@ -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, "") ?? ""; 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*/, ""); 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 11") 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 => { const greatestCommonDivisor = (left: number, right: number): number => {
let a = Math.abs(left); let a = Math.abs(left);
let b = Math.abs(right); let b = Math.abs(right);
@@ -866,6 +878,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const [videoOutfitVideoFile, setVideoOutfitVideoFile] = useState<File | null>(null); const [videoOutfitVideoFile, setVideoOutfitVideoFile] = useState<File | null>(null);
const [videoOutfitRefFile, setVideoOutfitRefFile] = useState<File | null>(null); const [videoOutfitRefFile, setVideoOutfitRefFile] = useState<File | null>(null);
const [isCloneSettingsCollapsed, setIsCloneSettingsCollapsed] = useState(false); const [isCloneSettingsCollapsed, setIsCloneSettingsCollapsed] = useState(false);
const [previewZoom, setPreviewZoom] = useState(1);
const [requirement, setRequirement] = useState(""); const [requirement, setRequirement] = useState("");
const [requirementImageMentionQuery, setRequirementImageMentionQuery] = useState<string | null>(null); const [requirementImageMentionQuery, setRequirementImageMentionQuery] = useState<string | null>(null);
const [cloneSettingName, setCloneSettingName] = useState("新建创作"); const [cloneSettingName, setCloneSettingName] = useState("新建创作");
@@ -1594,6 +1607,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const stamp = Date.now(); const stamp = Date.now();
for (const countKey of cloneSetCountOptions.map((o) => o.key)) { for (const countKey of cloneSetCountOptions.map((o) => o.key)) {
if (imageAbortRef.current.current) break;
const count = counts[countKey]; const count = counts[countKey];
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
if (imageAbortRef.current.current) break; if (imageAbortRef.current.current) break;
@@ -1603,7 +1617,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const { taskId } = await aiGenerationClient.createImageTask({ const { taskId } = await aiGenerationClient.createImageTask({
model: IMAGE_MODEL, model: IMAGE_MODEL,
prompt: fullPrompt, prompt: fullPrompt,
ratio: pRatio, ratio: normalizeRatioForApi(pRatio),
quality: pRatio.includes("720") ? "720P" : "1080P", quality: pRatio.includes("720") ? "720P" : "1080P",
gridMode: "single", gridMode: "single",
referenceUrls, referenceUrls,
@@ -1640,7 +1654,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
return; return;
} }
setResultFn(generatedUrls); setResultFn(generatedUrls);
setStatusFn(generatedUrls.some(Boolean) ? "done" : "idle"); setStatusFn(generatedUrls.some(Boolean) ? "done" : "failed");
} catch (err) { } catch (err) {
if (imageAbortRef.current.current) { if (imageAbortRef.current.current) {
setStatusFn("idle"); setStatusFn("idle");
@@ -1687,7 +1701,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const { taskId } = await aiGenerationClient.createImageTask({ const { taskId } = await aiGenerationClient.createImageTask({
model: IMAGE_MODEL, model: IMAGE_MODEL,
prompt, prompt,
ratio: pRatio, ratio: normalizeRatioForApi(pRatio),
quality: pRatio.includes("720") ? "720P" : "1080P", quality: pRatio.includes("720") ? "720P" : "1080P",
gridMode: "single", gridMode: "single",
referenceUrls, referenceUrls,
@@ -2010,7 +2024,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
clonePreviewCards.push({ clonePreviewCards.push({
id: `${countKey}-${i}`, 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}` : ""}`, label: `${info.label}${count > 1 ? ` ${i + 1}` : ""}`,
}); });
cloneIndex++; cloneIndex++;
@@ -2311,6 +2325,11 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
<span> <span>
AI <b></b> AI <b></b>
</span> </span>
<div className="clone-ai-preview-zoom">
<button type="button" onClick={() => setPreviewZoom((z) => Math.max(0.25, z - 0.1))} disabled={previewZoom <= 0.25} aria-label="缩小"></button>
<span>{Math.round(previewZoom * 100)}%</span>
<button type="button" onClick={() => setPreviewZoom((z) => Math.min(2, z + 0.1))} disabled={previewZoom >= 2} aria-label="放大">+</button>
</div>
</header> </header>
{cloneOutput === "video" ? ( {cloneOutput === "video" ? (
@@ -2433,8 +2452,9 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
) : ( ) : (
<> <>
{status === "done" ? ( {status === "done" ? (
<div className="clone-ai-preview-zoom-wrap" style={{ zoom: previewZoom }}>
<section className="clone-ai-preview-showcase" aria-label="生成结果"> <section className="clone-ai-preview-showcase" aria-label="生成结果">
<button type="button" className="clone-ai-main-result" onClick={() => openProductSetPreview(cloneOutput === "set" ? clonePreviewCards[0] : results[0])}> <button type="button" className="clone-ai-main-result" style={{ aspectRatio: parseRatioToAspectCss(ratio) }} onClick={() => openProductSetPreview(cloneOutput === "set" ? clonePreviewCards[0] : results[0])}>
<img src={productImages[0]?.src ?? (cloneOutput === "set" ? clonePreviewCards[0].src : results[0]?.src ?? "")} alt="上传商品原图" /> <img src={productImages[0]?.src ?? (cloneOutput === "set" ? clonePreviewCards[0].src : results[0]?.src ?? "")} alt="上传商品原图" />
<span></span> <span></span>
</button> </button>
@@ -2442,19 +2462,20 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
<div className="clone-ai-result-grid result-reveal"> <div className="clone-ai-result-grid result-reveal">
{cloneOutput === "set" ? ( {cloneOutput === "set" ? (
clonePreviewCards.map((card) => ( clonePreviewCards.map((card) => (
<button key={card.id} type="button" onClick={() => openProductSetPreview(card)}> <button key={card.id} type="button" style={{ aspectRatio: parseRatioToAspectCss(ratio) }} onClick={() => openProductSetPreview(card)}>
<img src={card.src} alt={card.label} /> <img src={card.src} alt={card.label} />
<span>{card.label}</span> <span>{card.label}</span>
</button> </button>
)) ))
) : results[0]?.src ? ( ) : results[0]?.src ? (
<button type="button" onClick={() => openProductSetPreview(results[0])}> <button type="button" style={{ aspectRatio: parseRatioToAspectCss(ratio) }} onClick={() => openProductSetPreview(results[0])}>
<img src={results[0].src} alt={selectedCloneOutput.label} /> <img src={results[0].src} alt={selectedCloneOutput.label} />
<span>{selectedCloneOutput.label}</span> <span>{selectedCloneOutput.label}</span>
</button> </button>
) : null} ) : null}
</div> </div>
</section> </section>
</div>
) : ( ) : (
<section className="clone-ai-empty-state" aria-live="polite"> <section className="clone-ai-empty-state" aria-live="polite">
{status === "generating" ? <LoadingOutlined /> : status === "failed" ? <FrownOutlined /> : <FileImageOutlined />} {status === "generating" ? <LoadingOutlined /> : status === "failed" ? <FrownOutlined /> : <FileImageOutlined />}
@@ -2643,7 +2664,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
) : null} ) : null}
{isSetTool ? setPreview : isDetail ? detailPreview : isTryOn ? tryOnPreview : isCloneTool ? (cloneOutput === "video" ? ( {isSetTool ? setPreview : isDetail ? detailPreview : isTryOn ? tryOnPreview : isCloneTool ? (cloneOutput === "video" ? (
<main className="product-clone-preview product-clone-preview--video" style={{ padding: 0, overflow: "hidden" }}> <main className="product-clone-preview product-clone-preview--video" style={{ padding: 0 }}>
<EcommerceVideoWorkspace <EcommerceVideoWorkspace
isAuthenticated={Boolean((_props as Record<string, unknown>).isAuthenticated)} isAuthenticated={Boolean((_props as Record<string, unknown>).isAuthenticated)}
productImageDataUrls={productImages.map((img) => img.src)} productImageDataUrls={productImages.map((img) => img.src)}
@@ -119,6 +119,7 @@ export default function EcommerceVideoWorkspace({
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [actionNotice, setActionNotice] = useState<string | null>(null); const [actionNotice, setActionNotice] = useState<string | null>(null);
const [previewMedia, setPreviewMedia] = useState<{ url: string; type: "image" | "video" } | null>(null); const [previewMedia, setPreviewMedia] = useState<{ url: string; type: "image" | "video" } | null>(null);
const [flowZoom, setFlowZoom] = useState(1);
const abortControllerRef = useRef<AbortController | null>(null); const abortControllerRef = useRef<AbortController | null>(null);
const renderAbortRef = useRef({ current: false }); const renderAbortRef = useRef({ current: false });
const setView = useAppStore((s) => s.setView); const setView = useAppStore((s) => s.setView);
@@ -600,6 +601,12 @@ export default function EcommerceVideoWorkspace({
})} })}
</div> </div>
<div className="ecom-video-flowbar__zoom">
<button type="button" onClick={() => setFlowZoom((z) => Math.max(0.25, z - 0.1))} disabled={flowZoom <= 0.25} aria-label="缩小"></button>
<span>{Math.round(flowZoom * 100)}%</span>
<button type="button" onClick={() => setFlowZoom((z) => Math.min(2, z + 0.1))} disabled={flowZoom >= 2} aria-label="放大">+</button>
</div>
<div className="ecom-video-flowbar__actions"> <div className="ecom-video-flowbar__actions">
{onOpenHistory ? ( {onOpenHistory ? (
<button type="button" className="ecom-video-flow-action ecom-video-flow-action--ghost" onClick={onOpenHistory} title="生成记录"> <button type="button" className="ecom-video-flow-action ecom-video-flow-action--ghost" onClick={onOpenHistory} title="生成记录">
@@ -644,9 +651,10 @@ export default function EcommerceVideoWorkspace({
{/* ── Flow canvas ──────────────────────────────────── */} {/* ── Flow canvas ──────────────────────────────────── */}
<section className="ecom-video-flow-canvas" aria-label="视频分镜流程图"> <section className="ecom-video-flow-canvas" aria-label="视频分镜流程图">
<div style={{ zoom: flowZoom, flexShrink: 0, display: "flex", alignItems: "flex-start", justifyContent: "center", minWidth: "max-content" }}>
{!sourceImage ? ( {!sourceImage ? (
<div className="ecom-video-empty"> <div className="ecom-video-empty">
<span></span> <span>"一键策划"</span>
</div> </div>
) : ( ) : (
<div className="ecom-video-tree"> <div className="ecom-video-tree">
@@ -762,6 +770,7 @@ export default function EcommerceVideoWorkspace({
</div> </div>
</div> </div>
)} )}
</div>
{/* ── Delivery dock ────────────────────────────── */} {/* ── Delivery dock ────────────────────────────── */}
{primaryVideo ? ( {primaryVideo ? (
+7 -7
View File
@@ -231,13 +231,13 @@ export const MODE_OPTIONS: WorkbenchOption[] = (Object.keys(MODE_META) as Workbe
})); }));
export const IMAGE_MODEL_OPTIONS: WorkbenchOption[] = [ export const IMAGE_MODEL_OPTIONS: WorkbenchOption[] = [
{ value: "wan2.7-image-pro", label: "wan 2.7 Pro 4K · 0.20 积分" }, { value: "wan2.7-image-pro", label: "wan 2.7 Pro 4K" },
{ value: "wan2.7-image", label: "wan 2.7 · 0.20 积分" }, { value: "wan2.7-image", label: "wan 2.7" },
{ value: "gpt-image-2", label: "GPT-Image-2 · 0.20 积分" }, { value: "gpt-image-2", label: "GPT-Image-2" },
{ value: "gpt-image-2-vip", label: "GPT-Image-2 VIP · 0.20 积分" }, { value: "gpt-image-2-vip", label: "GPT-Image-2 VIP" },
{ value: "nano-banana-pro", label: "Nano Banana Pro · 0.20 积分" }, { value: "nano-banana-pro", label: "Nano Banana Pro" },
{ value: "nano-banana-2", label: "Nano Banana 2 · 0.20 积分" }, { value: "nano-banana-2", label: "Nano Banana 2" },
{ value: "nano-banana-fast", label: "Nano Banana · 0.20 积分" }, { value: "nano-banana-fast", label: "Nano Banana" },
]; ];
export const VIDEO_MODEL_OPTIONS: WorkbenchOption[] = ENTERPRISE_VIDEO_MODEL_OPTIONS.map((option) => ({ ...option })); export const VIDEO_MODEL_OPTIONS: WorkbenchOption[] = ENTERPRISE_VIDEO_MODEL_OPTIONS.map((option) => ({ ...option }));
-1
View File
@@ -947,4 +947,3 @@
height: 100%; height: 100%;
cursor: crosshair; cursor: crosshair;
} }
}
+63 -12
View File
@@ -6,9 +6,13 @@
align-content: initial; align-content: initial;
justify-items: initial; justify-items: initial;
gap: 0; gap: 0;
overflow: hidden; overflow: auto;
background: #0e1014; 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 { .ecom-video-workspace {
@@ -20,6 +24,11 @@
overflow: hidden; overflow: hidden;
background: #0e1014; background: #0e1014;
color: #e5ebf4; color: #e5ebf4;
scrollbar-width: none;
-ms-overflow-style: none;
}
.ecom-video-workspace::-webkit-scrollbar {
display: none;
} }
.ecom-video-flowbar { .ecom-video-flowbar {
@@ -112,6 +121,42 @@
gap: 8px; 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 { .ecom-video-flowbar__error {
max-width: min(260px, 28vw); max-width: min(260px, 28vw);
overflow: hidden; overflow: hidden;
@@ -181,8 +226,13 @@
background: #101318; background: #101318;
padding: 32px 40px; padding: 32px 40px;
display: flex; display: flex;
align-items: stretch; align-items: flex-start;
justify-content: center; justify-content: center;
scrollbar-width: none;
-ms-overflow-style: none;
}
.ecom-video-flow-canvas::-webkit-scrollbar {
display: none;
} }
.ecom-video-flow-map { .ecom-video-flow-map {
@@ -418,22 +468,23 @@
width: 38px; width: 38px;
height: 38px; height: 38px;
place-items: center; place-items: center;
border: 1px solid #3d3020; border: 1px solid #00cc6a;
border-radius: 8px; border-radius: 9px;
background: #15181f; background: #00ff88;
color: #ffe1ad; color: #06130d;
font-size: 16px; font-size: 16px;
font-weight: 700;
cursor: pointer; cursor: pointer;
transition: transition:
transform 150ms ease, transform 150ms ease,
border-color 150ms ease, filter 150ms ease,
background-color 150ms ease; box-shadow 150ms ease;
} }
.ecom-video-flow-dock button:hover { .ecom-video-flow-dock button:hover {
border-color: #4d3a1a; filter: brightness(1.08);
background: #241c12; box-shadow: 0 2px 12px rgba(0, 255, 136, 0.25);
transform: translateY(-1px); transform: translateY(-2px);
} }
.ecom-video-flow-notice { .ecom-video-flow-notice {
+56 -5
View File
@@ -2792,12 +2792,18 @@
position: relative; position: relative;
display: grid; display: grid;
min-height: 0; min-height: 0;
overflow: hidden; overflow-y: auto;
align-content: center; overflow-x: hidden;
align-content: safe center;
justify-items: center; justify-items: center;
gap: 22px; gap: 22px;
background: #101115; background: #101115;
padding: 92px 46px 142px; 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 { .product-clone-page[data-tool="clone"] .clone-ai-preview-header {
@@ -2828,6 +2834,50 @@
color: #00ff88; 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 { .product-clone-page[data-tool="clone"] .clone-ai-empty-state {
display: grid; display: grid;
width: min(100%, 600px); width: min(100%, 600px);
@@ -2935,7 +2985,8 @@
} }
.product-clone-page[data-tool="clone"] .clone-ai-main-result { .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 { .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 { .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 { .product-clone-page[data-tool="clone"] .clone-ai-result-grid button:first-child {
grid-column: 1 / -1; grid-column: 1 / -1;
height: 240px;
} }
.product-clone-page[data-tool="clone"] .clone-ai-main-result span, .product-clone-page[data-tool="clone"] .clone-ai-main-result span,
+5 -5
View File
@@ -8,27 +8,27 @@ export const ENTERPRISE_WANXIANG_I2V_MODEL = "wan2.7-i2v";
export const ENTERPRISE_VIDEO_MODEL_OPTIONS = [ export const ENTERPRISE_VIDEO_MODEL_OPTIONS = [
{ {
value: HAPPY_HORSE_UI_MODEL, value: HAPPY_HORSE_UI_MODEL,
label: "HappyHorse 1.0 · 0.72 积分/秒起", label: "HappyHorse 1.0",
description: "自动匹配文生视频、首帧图生视频或参考图生视频", description: "自动匹配文生视频、首帧图生视频或参考图生视频",
}, },
{ {
value: VIDU_UI_MODEL, value: VIDU_UI_MODEL,
label: "Vidu Q3 Turbo · 0.40 积分/秒起", label: "Vidu Q3 Turbo",
description: "自动匹配文生视频或图生视频,支持16秒", description: "自动匹配文生视频或图生视频,支持16秒",
}, },
{ {
value: PIXVERSE_UI_MODEL, value: PIXVERSE_UI_MODEL,
label: "PixVerse V6 · 0.40 积分/秒起", label: "PixVerse V6",
description: "自动匹配文生视频或图生视频,擅长动作特效", description: "自动匹配文生视频或图生视频,擅长动作特效",
}, },
{ {
value: ENTERPRISE_WANXIANG_I2V_MODEL, value: ENTERPRISE_WANXIANG_I2V_MODEL,
label: "万相 图生视频 · 0.60 积分/秒起", label: "万相 图生视频",
description: "图生视频模型,支持首帧图驱动", description: "图生视频模型,支持首帧图驱动",
}, },
{ {
value: ENTERPRISE_KLING_MODEL, value: ENTERPRISE_KLING_MODEL,
label: "Kling V3 Omni · 0.60 积分/秒起", label: "Kling V3 Omni",
description: "支持文生视频、图生视频及多模态参考生成", description: "支持文生视频、图生视频及多模态参考生成",
}, },
] as const; ] as const;