Merge branch 'master' into feat/toolbox-preview-cards-and-responsive-polish

This commit is contained in:
2026-06-08 10:59:20 +00:00
39 changed files with 2047 additions and 101 deletions
@@ -0,0 +1,298 @@
import {
CheckCircleOutlined,
CloseCircleOutlined,
ExperimentOutlined,
FileSearchOutlined,
LoginOutlined,
ReloadOutlined,
} from "@ant-design/icons";
import { useCallback, useEffect, useMemo, useState } from "react";
import { betaApplicationClient, type BetaApplicationItem, type BetaApplicationStatus } from "../../api/betaApplicationClient";
import WorkspacePageShell from "../../components/WorkspacePageShell";
import type { WebUserSession } from "../../types";
import "../../styles/pages/beta-applications.css";
interface BetaApplicationsPageProps {
session: WebUserSession | null;
onOpenLogin: () => void;
}
type StatusFilter = BetaApplicationStatus | "";
const STATUS_OPTIONS: Array<{ value: StatusFilter; label: string }> = [
{ value: "pending", label: "待审核" },
{ value: "approved", label: "已通过" },
{ value: "rejected", label: "已驳回" },
{ value: "", label: "全部" },
];
const STATUS_LABEL: Record<BetaApplicationStatus, string> = {
pending: "待审核",
approved: "已通过",
rejected: "已驳回",
};
function canReviewBetaApplications(session: WebUserSession | null): boolean {
const role = String(session?.user.role || "").trim().toLowerCase();
const username = String(session?.user.username || "").trim().toLowerCase();
return role === "admin" || username === "xqy1912";
}
function formatDate(value?: string | null): string {
if (!value) return "暂无时间";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
return date.toLocaleString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
});
}
function valueOrEmpty(value?: string | null): string {
return value?.trim() || "未填写";
}
function joinValues(values: string[]): string {
return values.length ? values.join("、") : "未选择";
}
function DetailField({ label, value, wide }: { label: string; value: string; wide?: boolean }) {
return (
<div className={`beta-admin-field${wide ? " beta-admin-field--wide" : ""}`}>
<span>{label}</span>
<strong>{value}</strong>
</div>
);
}
export default function BetaApplicationsPage({ session, onOpenLogin }: BetaApplicationsPageProps) {
const allowed = canReviewBetaApplications(session);
const [status, setStatus] = useState<StatusFilter>("pending");
const [applications, setApplications] = useState<BetaApplicationItem[]>([]);
const [selectedId, setSelectedId] = useState<number | null>(null);
const [reviewNote, setReviewNote] = useState("");
const [loading, setLoading] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const selectedApplication = useMemo(
() => applications.find((item) => item.id === selectedId) ?? applications[0] ?? null,
[applications, selectedId],
);
const load = useCallback(async () => {
if (!allowed) return;
setLoading(true);
setError(null);
try {
const items = await betaApplicationClient.listAdminApplications(status);
setApplications(items);
setSelectedId((current) =>
current && items.some((item) => item.id === current) ? current : (items[0]?.id ?? null),
);
} catch (loadError) {
setApplications([]);
setError(loadError instanceof Error ? loadError.message : "内测申请列表加载失败");
} finally {
setLoading(false);
}
}, [allowed, status]);
useEffect(() => {
void load();
}, [load]);
const handleDecision = async (action: "approve" | "reject") => {
if (!selectedApplication || selectedApplication.status !== "pending" || submitting) return;
setSubmitting(true);
setError(null);
try {
await betaApplicationClient.reviewApplication(selectedApplication.id, action, reviewNote.trim());
setReviewNote("");
await load();
} catch (submitError) {
setError(submitError instanceof Error ? submitError.message : "审核操作失败");
} finally {
setSubmitting(false);
}
};
if (!session) {
return (
<WorkspacePageShell title="内测申请审核" fullWidth className="beta-admin-page page-motion">
<section className="beta-admin-access">
<LoginOutlined />
<h1></h1>
<p> xqy1912</p>
<button type="button" onClick={onOpenLogin}> / </button>
</section>
</WorkspacePageShell>
);
}
if (!allowed) {
return (
<WorkspacePageShell title="内测申请审核" fullWidth className="beta-admin-page page-motion">
<section className="beta-admin-access">
<FileSearchOutlined />
<h1></h1>
<p> admin xqy1912 </p>
</section>
</WorkspacePageShell>
);
}
return (
<WorkspacePageShell title="内测申请审核" fullWidth className="beta-admin-page page-motion">
<div className="beta-admin-page__inner">
<section className="beta-admin-toolbar">
<div>
<span></span>
<h1></h1>
<p></p>
</div>
<button type="button" onClick={() => void load()} disabled={loading}>
<ReloadOutlined />
</button>
</section>
<div className="beta-admin-status-tabs" role="tablist" aria-label="内测申请状态">
{STATUS_OPTIONS.map((option) => (
<button
key={option.value || "all"}
type="button"
role="tab"
aria-selected={status === option.value}
className={status === option.value ? "is-active" : ""}
onClick={() => setStatus(option.value)}
>
{option.label}
</button>
))}
</div>
{error ? <p className="beta-admin-error">{error}</p> : null}
<section className="beta-admin-layout">
<aside className="beta-admin-list" aria-label="内测申请列表">
{loading ? <div className="beta-admin-list__empty">...</div> : null}
{!loading && applications.length === 0 ? (
<div className="beta-admin-list__empty"></div>
) : null}
{applications.map((item) => (
<button
key={item.id}
type="button"
className={`beta-admin-list__item${item.id === selectedApplication?.id ? " is-active" : ""}`}
onClick={() => setSelectedId(item.id)}
>
<span className={`beta-admin-status beta-admin-status--${item.status}`}>{STATUS_LABEL[item.status]}</span>
<strong>{item.name || item.username || `申请 #${item.id}`}</strong>
<small>{item.industry || "未填写行业"} · {formatDate(item.createdAt)}</small>
</button>
))}
</aside>
{selectedApplication ? (
<article className="beta-admin-detail">
<header className="beta-admin-detail__header">
<div>
<span><ExperimentOutlined /> {STATUS_LABEL[selectedApplication.status]}</span>
<h2>{selectedApplication.name || "未填写姓名"}</h2>
<p>{selectedApplication.selfStatement || "申请人未填写自述。"}</p>
</div>
{selectedApplication.inviteCode ? (
<strong className="beta-admin-code">{selectedApplication.inviteCode}</strong>
) : null}
</header>
<section className="beta-admin-form-card">
<h3></h3>
<div className="beta-admin-field-grid">
<DetailField label="姓名 / 常用昵称" value={valueOrEmpty(selectedApplication.name)} />
<DetailField label="接收内测码邮箱" value={valueOrEmpty(selectedApplication.email)} />
<DetailField label="联系手机号码" value={valueOrEmpty(selectedApplication.phone)} />
<DetailField label="微信账号" value={valueOrEmpty(selectedApplication.wechat)} />
<DetailField label="所在行业 / 职业" value={valueOrEmpty(selectedApplication.industry)} />
<DetailField label="所属公司 / 机构" value={valueOrEmpty(selectedApplication.company)} />
<DetailField label="所在城市" value={valueOrEmpty(selectedApplication.city)} />
<DetailField label="关联账号" value={selectedApplication.username || `UID ${selectedApplication.userId ?? "未登录提交"}`} />
<DetailField label="提交时间" value={formatDate(selectedApplication.createdAt)} />
</div>
</section>
<section className="beta-admin-form-card">
<h3>AI 使</h3>
<div className="beta-admin-field-grid">
<DetailField label="常用 AI 创作工具" value={valueOrEmpty(selectedApplication.aiTools)} wide />
<DetailField label="AI 内容创作从业时长" value={valueOrEmpty(selectedApplication.aiDuration)} />
<DetailField label="是否深耕相关赛道" value={valueOrEmpty(selectedApplication.aiTrack)} />
<DetailField label="日常主要创作方向" value={joinValues(selectedApplication.aiDirection)} wide />
</div>
</section>
<section className="beta-admin-form-card">
<h3>使</h3>
<div className="beta-admin-field-grid">
<DetailField label="每周稳定使用次数" value={valueOrEmpty(selectedApplication.weeklyUsage)} />
<DetailField label="反馈意愿" value={valueOrEmpty(selectedApplication.feedbackWilling)} />
<DetailField label="最想体验功能" value={joinValues(selectedApplication.wantFeature)} wide />
</div>
</section>
<section className="beta-admin-form-card">
<h3></h3>
<p className="beta-admin-statement">{selectedApplication.selfStatement || "未填写"}</p>
<div className="beta-admin-field-grid">
<DetailField label="申请人确认签字" value={valueOrEmpty(selectedApplication.signature)} />
<DetailField label="申请填写日期" value={valueOrEmpty(selectedApplication.applicationDate)} />
<DetailField label="同意规则" value={selectedApplication.agreeRules ? "已同意" : "未同意"} />
<DetailField label="IP" value={valueOrEmpty(selectedApplication.ipAddress)} />
<DetailField label="客户端" value={valueOrEmpty(selectedApplication.userAgent)} wide />
</div>
</section>
{selectedApplication.status !== "pending" ? (
<section className="beta-admin-form-card">
<h3></h3>
<div className="beta-admin-field-grid">
<DetailField label="审核人" value={selectedApplication.reviewerUsername || `UID ${selectedApplication.reviewedBy ?? "-"}`} />
<DetailField label="审核时间" value={formatDate(selectedApplication.reviewedAt)} />
<DetailField label="审核备注" value={valueOrEmpty(selectedApplication.reviewNote)} wide />
</div>
</section>
) : (
<section className="beta-admin-review-box">
<label>
<span></span>
<textarea
value={reviewNote}
onChange={(event) => setReviewNote(event.target.value)}
placeholder="填写通过说明或驳回原因;驳回时该备注会作为用户通知内容。"
/>
</label>
<div className="beta-admin-actions">
<button type="button" disabled={submitting} onClick={() => void handleDecision("reject")}>
<CloseCircleOutlined />
</button>
<button type="button" disabled={submitting} onClick={() => void handleDecision("approve")}>
<CheckCircleOutlined />
</button>
</div>
</section>
)}
</article>
) : (
<div className="beta-admin-detail beta-admin-detail--empty"></div>
)}
</section>
</div>
</WorkspacePageShell>
);
}
+7 -7
View File
@@ -58,13 +58,13 @@ export const defaultTextModelId = textModelOptions[0].id;
// --- Image model options ---
export const imageModelOptions: CanvasOption[] = [
{ value: "wan2.7-image", label: "wan 2.7 · 0.20 积分" },
{ value: "wan2.7-image-pro", label: "wan 2.7 Pro · 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", label: "wan 2.7" },
{ value: "wan2.7-image-pro", label: "wan 2.7 Pro" },
{ value: "gpt-image-2", label: "omni-GPT" },
{ value: "gpt-image-2-vip", label: "omni-GPT VIP" },
{ value: "nano-banana-pro", label: "omni-水果 Pro" },
{ value: "nano-banana-2", label: "omni-水果 2" },
{ value: "nano-banana-fast", label: "omni-水果" },
];
export const imageRatioOptions: CanvasOption[] = [
@@ -10,6 +10,7 @@ import { useEffect, useMemo, useRef, useState, type ChangeEvent } from "react";
import { aiGenerationClient } from "../../api/aiGenerationClient";
import { communityClient } from "../../api/communityClient";
import WorkspacePageShell from "../../components/WorkspacePageShell";
import "../../styles/pages/compliance.css";
import type { WebCanvasWorkflow, WebUserSession } from "../../types";
import { getWorkflowCoverUrl, isCanvasWorkflow } from "../community/communityCaseUtils";
import { canManageCommunityCases } from "./communityPermissions";
@@ -11,6 +11,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { communityClient, type ServerCommunityCase } from "../../api/communityClient";
import { reportClient, type AdminReportItem } from "../../api/reportClient";
import WorkspacePageShell from "../../components/WorkspacePageShell";
import "../../styles/pages/compliance.css";
import type { WebUserSession } from "../../types";
import { canManageCommunityCases, canReviewCommunity } from "./communityPermissions";
+29 -4
View File
@@ -842,6 +842,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const skipInitialCloneAutoSaveRef = useRef(true);
const skipNextCloneAutoSaveRef = useRef(false);
const [activeTool, setActiveTool] = useState<ProductKitToolKey>("clone");
useEffect(() => { setPreviewZoom(1); }, [activeTool]);
const [setImages, setSetImages] = useState<CloneImageItem[]>([]);
const [productSetPlatform, setProductSetPlatform] = useState(platformOptions[0]);
const [productSetMarket, setProductSetMarket] = useState(marketOptions[0]);
@@ -882,6 +883,30 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const [videoOutfitRefFile, setVideoOutfitRefFile] = useState<File | null>(null);
const [isCloneSettingsCollapsed, setIsCloneSettingsCollapsed] = useState(false);
const [previewZoom, setPreviewZoom] = useState(1);
const handlePreviewWheel = (event: React.WheelEvent<HTMLElement>) => {
if (!event.currentTarget) return;
event.preventDefault();
const container = event.currentTarget as HTMLElement;
const rect = container.getBoundingClientRect();
const cursorX = event.clientX - rect.left;
const cursorY = event.clientY - rect.top;
const zoomDelta = event.deltaY < 0 ? 1.08 : 0.92;
const nextZoom = Math.min(2, Math.max(0.25, previewZoom * zoomDelta));
if (nextZoom === previewZoom) return;
const contentX = (cursorX + container.scrollLeft) / previewZoom;
const contentY = (cursorY + container.scrollTop) / previewZoom;
setPreviewZoom(nextZoom);
requestAnimationFrame(() => {
container.scrollLeft = contentX * nextZoom - cursorX;
container.scrollTop = contentY * nextZoom - cursorY;
});
};
const [requirement, setRequirement] = useState("");
const [requirementImageMentionQuery, setRequirementImageMentionQuery] = useState<string | null>(null);
const [cloneSettingName, setCloneSettingName] = useState("新建创作");
@@ -2332,7 +2357,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
);
const setPreview = (
<main className="product-clone-preview product-clone-preview--set" aria-label="AI商品套图预览">
<main className="product-clone-preview product-clone-preview--set" aria-label="AI商品套图预览" onWheel={handlePreviewWheel}>
<div className="product-clone-preview__headline">
<h1></h1>
<p>
@@ -2400,7 +2425,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
);
const clonePreview = (
<main className="product-clone-preview clone-ai-preview" aria-label="电商AI作图预览">
<main className="product-clone-preview clone-ai-preview" aria-label="电商AI作图预览" onWheel={handlePreviewWheel}>
<header className="clone-ai-preview-header">
<strong></strong>
<span>
@@ -2610,7 +2635,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
);
const detailPreview = (
<main className="product-clone-preview product-clone-preview--detail" aria-label="A+详情预览">
<main className="product-clone-preview product-clone-preview--detail" aria-label="A+详情预览" onWheel={handlePreviewWheel}>
<div className="product-clone-preview__headline">
<h1>A+/</h1>
<p>
@@ -2647,7 +2672,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
);
const tryOnPreview = (
<main className="product-clone-preview product-clone-preview--try-on" aria-label="服饰穿戴预览">
<main className="product-clone-preview product-clone-preview--try-on" aria-label="服饰穿戴预览" onWheel={handlePreviewWheel}>
<div className="product-clone-preview__headline">
<h1>AI服饰穿戴</h1>
<p>姿</p>
+1
View File
@@ -2,6 +2,7 @@ import { CheckCircleOutlined, FlagOutlined, MailOutlined, PhoneOutlined } from "
import { useEffect, useState, type FormEvent } from "react";
import { publicConfigClient, type WebPublicConfig } from "../../api/publicConfigClient";
import { reportClient, type ReportInput } from "../../api/reportClient";
import "../../styles/pages/compliance.css";
type SubmitState = "idle" | "loading" | "success" | "error";
+94 -12
View File
@@ -67,7 +67,6 @@ import { downloadResultAsset } from "./workbenchDownload";
import { translateTaskError } from "../../utils/translateTaskError";
import {
buildLocalTimeoutMessage,
formatTextTokenUsage,
getTaskTimeoutPolicy,
isTaskLocallyTimedOut,
} from "../../utils/taskLifecycle";
@@ -79,10 +78,12 @@ import {
import { isViduModel } from "../../utils/viduRouting";
import { isPixverseModel } from "../../utils/pixverseRouting";
import { resolveVideoRequestModel } from "../../utils/resolveVideoModel";
import { ENTERPRISE_DEFAULT_VIDEO_MODEL } from "../../utils/enterpriseVideoPolicy";
import { calculateEnterpriseVideoCredits, ENTERPRISE_DEFAULT_VIDEO_MODEL } from "../../utils/enterpriseVideoPolicy";
import {
getImageQualityOptions,
getImageQualityOptionsForContext,
getDefaultImageQuality,
getDefaultImageQualityForContext,
getVideoQualityOptions,
getDefaultVideoQuality,
getVideoQualityLabel,
@@ -221,6 +222,12 @@ const MODE_ICONS: Record<WorkbenchMode, ReactNode> = {
video: <VideoCameraOutlined />,
};
function formatCreditValue(value: number): string {
if (!Number.isFinite(value)) return "-";
if (value >= 100) return Math.round(value).toLocaleString("zh-CN");
return Number(value.toFixed(2)).toString();
}
function WorkbenchPage({
isAuthenticated,
session,
@@ -464,11 +471,72 @@ function WorkbenchPage({
setSidebarCollapsed(!hasSidebarRecords);
}, [hasSidebarRecords]);
const imageQualityOptions = useMemo(() => getImageQualityOptions(imageModel), [imageModel]);
const hasImageReferences = activeMode === "image" && referenceItems.some((item) => item.kind === "image");
const isImageGridMode = activeMode === "image" && imageGridMode !== "single";
const imageQualityContext = useMemo(
() => ({
hasReferenceImages: hasImageReferences,
isGridMode: isImageGridMode,
}),
[hasImageReferences, isImageGridMode],
);
const imageQualityOptions = useMemo(
() => getImageQualityOptionsForContext(imageModel, imageQualityContext),
[imageModel, imageQualityContext],
);
const imageGridModeOptions = useMemo(
() =>
String(imageModel || "").toLowerCase().startsWith("wan2.7-")
? GRID_MODE_OPTIONS.filter((option) => option.value !== "grid-25")
: GRID_MODE_OPTIONS,
[imageModel],
);
const videoQualityOptions = getVideoQualityOptions(videoModel);
const videoQualityLabel = getVideoQualityLabel(videoModel, videoQuality);
const imageSettingsSummary = `${imageRatio} / ${imageQuality}`;
const billingEstimate = useMemo(() => {
if (activeMode === "image") {
return {
label: "预计 20 积分",
title: `图片生成按任务计费:${activeModel}${imageSettingsSummary},预计 20 积分`,
};
}
if (activeMode === "video") {
try {
const durationSeconds = Math.max(1, Math.ceil(Number(videoDuration) || 1));
const credits = calculateEnterpriseVideoCredits({
model: activeModelValue,
resolution: videoQuality,
durationSeconds,
muted: false,
hasReferenceVideo: referenceItems.some((item) => item.kind === "video"),
});
return {
label: `预计 ${formatCreditValue(credits)} 积分`,
title: `${activeModel}${videoQualityLabel}${durationSeconds} 秒,预计 ${formatCreditValue(credits)} 积分`,
};
} catch {
return {
label: "计费以提交后为准",
title: "当前模型的预估计费暂不可用,实际扣费以服务端结算为准",
};
}
}
return {
label: "按 Token 结算",
title: "文本对话按输入、输出 Token 实际用量结算,完成后显示本次积分",
};
}, [
activeMode,
activeModel,
activeModelValue,
imageSettingsSummary,
referenceItems,
videoDuration,
videoQuality,
videoQualityLabel,
]);
const composerPlaceholder =
referenceItems.length > 0 ? `${toolTheme.placeholder},可输入 @ 引用参考内容` : toolTheme.placeholder;
const dropdownDirection = hasActivatedWorkspace ? "up" : "down";
@@ -1158,9 +1226,15 @@ function WorkbenchPage({
useEffect(() => {
if (!imageQualityOptions.some((option) => option.value === imageQuality)) {
setImageQuality(getDefaultImageQuality(imageModel));
setImageQuality(getDefaultImageQualityForContext(imageModel, imageQualityContext));
}
}, [imageModel, imageQuality, imageQualityOptions]);
}, [imageModel, imageQuality, imageQualityContext, imageQualityOptions]);
useEffect(() => {
if (!imageGridModeOptions.some((option) => option.value === imageGridMode)) {
setImageGridMode("single");
}
}, [imageGridMode, imageGridModeOptions]);
useEffect(() => {
if (activeMode !== "video" || videoFrameMode !== "start-end" || referenceItems.length <= 2) return;
@@ -2585,7 +2659,15 @@ function WorkbenchPage({
}
};
const sendDisabled = !inputValue.trim() || (activeMode !== "chat" && getActiveGenerationTaskCount(getGenerationUserKey(session?.user.id)) >= 3);
const activeGenerationCount = getActiveGenerationTaskCount(getGenerationUserKey(session?.user.id));
const generationLimitReached = activeMode !== "chat" && activeGenerationCount >= 3;
const promptIsEmpty = !inputValue.trim();
const sendDisabled = promptIsEmpty || generationLimitReached;
const sendButtonTitle = promptIsEmpty
? "输入内容后可发送"
: generationLimitReached
? `当前已有 ${activeGenerationCount} 个任务进行中,请等待任一任务完成`
: billingEstimate.title;
const suggestedPrompts = [
{ text: "画一个赛博朋克风格的城市夜景", mode: "image" as WorkbenchMode },
@@ -2850,7 +2932,7 @@ function WorkbenchPage({
<SelectChip
chipId="image-grid-mode"
value={imageGridMode}
options={GRID_MODE_OPTIONS}
options={imageGridModeOptions}
disabled={disabled}
isOpen={toolbarMenuId === "image-grid-mode"}
onToggle={() => toggleToolbarMenu("image-grid-mode")}
@@ -2922,10 +3004,15 @@ function WorkbenchPage({
)}
</div>
<div className="wb-composer__toolbar-right">
<span className="wb-composer__billing-estimate" title={billingEstimate.title}>
{billingEstimate.label}
</span>
<button
type="button"
className={`wb-composer__send-primary${isGenerating ? " is-loading" : ""}`}
disabled={sendDisabled || isGenerating}
title={isGenerating ? "任务处理中" : sendButtonTitle}
aria-label={isGenerating ? "任务处理中" : sendButtonTitle}
onClick={() => {
if (getCachedRole() === "admin") console.log("[ai/workbench-send-click]", {
mode: activeMode,
@@ -3231,11 +3318,6 @@ function WorkbenchPage({
<span>{message.taskStatusLabel || generationStatus}</span>
</div>
)}
{message.role === "assistant" && message.mode === "chat" && message.status === "completed" && (
<div className="ai-chat-task-billing-note">
{formatTextTokenUsage(message.taskUsage)}
</div>
)}
{(message.resultUrl || (message.result && message.status !== "thinking")) && (
<ResultCard
message={message}
+6 -6
View File
@@ -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" },
{ value: "wan2.7-image-pro", label: "wan 2.7 Pro" },
{ 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" },
{ value: "gpt-image-2", label: "omni-GPT" },
{ value: "gpt-image-2-vip", label: "omni-GPT VIP" },
{ value: "nano-banana-pro", label: "omni-水果 Pro" },
{ value: "nano-banana-2", label: "omni-水果 2" },
{ value: "nano-banana-fast", label: "omni-水果" },
];
export const VIDEO_MODEL_OPTIONS: WorkbenchOption[] = ENTERPRISE_VIDEO_MODEL_OPTIONS.map((option) => ({ ...option }));