import { Input, InputNumber, Segmented, Select, Switch } from "antd"; import type { ReactNode } from "react"; import { defaultImageCapabilityConfig, defaultModelCapabilityConfig, type ImageCapabilityConfig, type ModelCapabilityConfig, type VideoCapabilityConfig } from "@/lib/model-capabilities"; import type { ModelProtocol } from "@/lib/model-protocols"; import { VIDEO_RESOLUTION_CAPABILITY_OPTIONS } from "@/lib/video-generation-options"; const ratioOptions = ["16:9", "9:16", "1:1", "4:3", "3:4", "21:9"]; const operationOptions = [ { label: "文生视频", value: "text_to_video" }, { label: "图生视频", value: "image_to_video" }, { label: "视频续写", value: "extend" }, { label: "局部修改", value: "inpaint" }, { label: "元素替换", value: "replace_element" }, { label: "运镜调整", value: "camera_motion" }, { label: "风格迁移", value: "style_transfer" }, { label: "音频生视频", value: "audio_to_video" }, ]; type Props = { value?: ModelCapabilityConfig; onChange?: (value: ModelCapabilityConfig) => void; protocol?: ModelProtocol; capability?: "image" | "video"; model?: string; disabled?: boolean; }; export function ModelCapabilityEditor({ value, onChange, protocol, capability = "video", model = "", disabled = false }: Props) { if (capability === "image") { return ; } const profile = value?.video || defaultModelCapabilityConfig(protocol).video!; const update = (patch: Partial) => onChange?.({ version: 1, video: { ...profile, ...patch } }); const updateReferences = (patch: Partial) => update({ references: { ...profile.references, ...patch } }); const updateDuration = (patch: Partial) => update({ duration: { ...profile.duration, ...patch } }); const durationValues = (profile.duration.values || []).join(","); const resolutionOptions = Array.from(new Set([...VIDEO_RESOLUTION_CAPABILITY_OPTIONS, ...profile.resolutions])); return (
视频能力参数
这些参数会同步到创造页、画布和生成校验
协议模板可继续调整
updateReferences({ promptMaxChars: value || 1 })} /> updateReferences({ minImages: value || 0 })} /> { const maxImages = value || 0; updateReferences({ maxImages, minImages: Math.min(profile.references.minImages, maxImages) }); }} /> updateReferences({ maxImageBytes: mbToBytes(value) })} /> updateReferences({ maxVideos: value || 0 })} /> updateReferences({ maxVideoBytes: mbToBytes(value) })} /> updateReferences({ maxVideoDurationSeconds: value || 0 })} /> updateReferences({ maxAudios: value || 0 })} /> updateReferences({ maxAudioBytes: mbToBytes(value) })} /> updateReferences({ maxAudioDurationSeconds: value || 0 })} />
updateDuration(value === "enum" ? { selection: "enum", values: profile.duration.values?.length ? profile.duration.values : [profile.duration.default] } : { selection: "range", min: profile.duration.min || 1, max: profile.duration.max || 15, step: profile.duration.step || 1 })} /> {profile.duration.selection === "enum" ? ( updateDuration({ values: parseIntegerList(event.target.value), default: parseIntegerList(event.target.value)[0] || profile.duration.default })} /> ) : (
updateDuration({ min: value || 1 })} /> updateDuration({ max: value || 1 })} /> updateDuration({ step: value || 1 })} /> updateDuration({ default: value || 1 })} />
)}
({ label: item, value: item }))} onChange={(defaultRatio) => update({ defaultRatio })} /> ({ label: item.toUpperCase(), value: item }))} onChange={(defaultResolution) => update({ defaultResolution })} />
update({ generateAudio })} /> update({ watermark })} />
profile.operations.includes(item.value))} onChange={(defaultOperation) => update({ defaultOperation })} />
); } function ImageCapabilityEditor({ value, onChange, protocol, model, disabled }: Required> & Pick) { const profile = value?.image || defaultImageCapabilityConfig(protocol, model); const update = (patch: Partial) => onChange?.({ version: 1, image: { ...profile, ...patch } }); const updateReferences = (patch: Partial) => update({ references: { ...profile.references, ...patch } }); const updateSize = (patch: Partial) => update({ size: { ...profile.size, ...patch } }); const updateQuality = (patch: Partial) => update({ quality: { ...profile.quality, ...patch } }); return (
图片能力参数
生成界面和后端请求都会按此处裁剪参数
当前模型独立生效
updateReferences({ promptMaxChars: promptMaxChars || 1 })} /> updateReferences({ maxImages: maxImages || 0 })} /> updateReferences({ maxImageBytes: mbToBytes(maxImageBytes) })} /> update({ maxOutputs: maxOutputs || 1 })} />
updateReferences({ maskSupported })} />
{ const parameter = value as ImageCapabilityConfig["size"]["parameter"]; updateSize(parameter === "none" ? { parameter, values: [], default: "auto", allowCustom: false } : { parameter, values: profile.size.values.length ? profile.size.values : ["1:1"], default: profile.size.default === "auto" ? "1:1" : profile.size.default }); }} /> {profile.size.parameter !== "none" ? <> ({ label: item, value: item }))} onChange={(defaultValue) => updateSize({ default: defaultValue })} /> updateSize({ allowCustom })} />
: null} updateQuality({ supported })} /> {profile.quality.supported ?
({ label: item, value: item }))} onChange={(defaultValue) => updateQuality({ default: defaultValue })} />
: null} update({ transparentBackground })} />
update({ responseFormat: { supported } })} /> update({ outputFormat: { supported } })} />
); } function CapabilityGroup({ title, children }: { title: string; children: ReactNode }) { return
{title}
{children}
; } function Field({ label, children }: { label: string; children: ReactNode }) { return ; } function NumberField({ label, value, min, max, disabled, onChange }: { label: string; value?: number; min: number; max?: number; disabled: boolean; onChange: (value: number | null) => void }) { return ; } function BooleanField({ label, value, disabled, onChange }: { label: string; value: { supported: boolean; default: boolean }; disabled: boolean; onChange: (value: { supported: boolean; default: boolean }) => void }) { return
{label}
支持该参数
onChange({ ...value, supported })} /> onChange({ ...value, default: defaultValue })} />
; } function ParameterField({ label, description, supported, disabled, onChange }: { label: string; description: string; supported: boolean; disabled: boolean; onChange: (supported: boolean) => void }) { return
{label}
{description}
; } function bytesToMB(value: number) { return value ? Math.round((value / (1024 * 1024)) * 10) / 10 : 0; } function mbToBytes(value: number | null) { return Math.max(0, Math.round(Number(value || 0) * 1024 * 1024)); } function parseIntegerList(value: string) { return Array.from(new Set(value.split(",").map((item) => Number(item.trim())).filter((item) => Number.isInteger(item) && item > 0))).sort((left, right) => left - right); }