import { ImageSizePresetsEditor } from "./image-size-presets-editor";
import { imageSizeConfigWithPresets, imageSizePresets } from "@/lib/image-size-presets";
import { Input, InputNumber, Select } from "antd";
import { Switch } from "@/components/ui/base/switch";
import { SegmentedControl } from "@/components/ui/base/segmented-control";
import type { ReactNode } from "react";
import { defaultImageCapabilityConfig, defaultModelCapabilityConfig, normalizeModelCapabilityConfig, type ImageCapabilityConfig, type ModelCapabilityConfig, type TextCapabilityConfig, 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: "reference_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" },
];
function ImageSizeHelp() {
return
按分辨率配置支持的比例,尺寸自动换算。size 发送像素,aspect_ratio 发送比例;独立分辨率由模型协议适配。实际输出尺寸以上游为准。
;
}
type Props = {
value?: ModelCapabilityConfig;
onChange?: (value: ModelCapabilityConfig) => void;
protocol?: ModelProtocol;
capability?: "text" | "image" | "video";
model?: string;
disabled?: boolean;
section?: "all" | "protocol" | "references";
};
export function ModelCapabilityEditor({ value, onChange, protocol, capability = "video", model = "", disabled = false, section = "all" }: Props) {
if (capability === "text") {
return ;
}
if (capability === "image") {
return ;
}
const profile = normalizeModelCapabilityConfig(value || 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]));
if (section === "references") {
return (
updateReferences({ minImages: value || 0 })} />
{
const maxImages = value || 0;
updateReferences({ maxImages, minImages: Math.min(profile.references.minImages, maxImages) });
}}
/>
updateReferences({ maxImageBytes: mbToBytes(value) })} />
updateReferences({ maxAudios: value || 0 })} />
updateReferences({ maxAudioBytes: mbToBytes(value) })} />
updateReferences({ maxAudioDurationSeconds: value || 0 })} />
update({ generateAudio })} />
updateReferences({ maxVideos: value || 0 })} />
updateReferences({ maxVideoBytes: mbToBytes(value) })} />
updateReferences({ maxVideoDurationSeconds: value || 0 })} />
updateReferences({ promptMaxChars: value || 1 })} />
update({ watermark })} />
);
}
if (section === "protocol") {
return (
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 })} />
)}
);
}
return (
视频能力参数
这些参数会同步到创造页、画布和生成校验
协议模板可继续调整
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 })} />
)}
{section === "all" ? (
updateReferences({ maxVideos: value || 0 })} />
updateReferences({ maxVideoBytes: mbToBytes(value) })} />
updateReferences({ maxVideoDurationSeconds: value || 0 })} />
) : null}
{section === "all" ? (
<>
updateReferences({ minImages: value || 0 })} />
{
const maxImages = value || 0;
updateReferences({ maxImages, minImages: Math.min(profile.references.minImages, maxImages) });
}}
/>
updateReferences({ maxImageBytes: mbToBytes(value) })} />
updateReferences({ maxAudios: value || 0 })} />
updateReferences({ maxAudioBytes: mbToBytes(value) })} />
updateReferences({ maxAudioDurationSeconds: value || 0 })} />
update({ generateAudio })} />
updateReferences({ promptMaxChars: value || 1 })} />
update({ watermark })} />
>
) : null}
);
}
function TextCapabilityEditor({ value, onChange, protocol, disabled, section }: Pick) {
const profile = normalizeModelCapabilityConfig(value || defaultModelCapabilityConfig(protocol)).text!;
const update = (patch: Partial) => {
onChange?.({ version: 1, text: { ...profile, ...patch } });
};
const updateReferences = (patch: Partial) => {
update({ references: { ...profile.references, ...patch } });
};
if (section === "protocol") {
return (
update({ streaming })} />
);
}
if (section === "references") {
const contextWindowFields = (
update({ contextWindowTokens: next || 4_096 })} />
update({ maxOutputTokens: next || 256 })} />
);
return (
{contextWindowFields}
updateReferences({ maxImages: next || 0 })} />
updateReferences({ maxImageBytes: mbToBytes(next) })} />
updateReferences({ maxVideos: next || 0 })} />
updateReferences({ maxVideoBytes: mbToBytes(next) })} />
updateReferences({ promptMaxChars: next || 1 })} />
);
}
return (
文本理解能力
默认不假设支持图片或视频,只有明确配置后相关请求才会进入该模型。
update({ contextWindowTokens: next || 4_096 })} />
update({ maxOutputTokens: next || 256 })} />
updateReferences({ maxImages: next || 0 })} />
updateReferences({ maxImageBytes: mbToBytes(next) })} />
updateReferences({ maxVideos: next || 0 })} />
updateReferences({ maxVideoBytes: mbToBytes(next) })} />
updateReferences({ promptMaxChars: next || 1 })} />
);
}
function ImageCapabilityEditor({ value, onChange, protocol, model, disabled, section }: Required> & Pick) {
const profile = normalizeModelCapabilityConfig(value || { version: 1, image: defaultImageCapabilityConfig(protocol, model) }).image!;
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 } });
if (section === "references") {
return (
updateReferences({ maxImages: maxImages || 0 })} />
updateReferences({ maxImageBytes: mbToBytes(maxImageBytes) })} />
updateReferences({ maskSupported })} />
updateReferences({ promptMaxChars: promptMaxChars || 1 })} />
);
}
if (section === "protocol") {
return (
{
const parameter = value as ImageCapabilityConfig["size"]["parameter"];
updateSize(
parameter === "none"
? { parameter, values: [], presets: undefined, default: "auto", allowCustom: false }
: imageSizeConfigWithPresets({ ...profile, size: { ...profile.size, parameter } }, imageSizePresets(profile)),
);
}}
/>
{profile.size.parameter !== "none" ? (
<>
updateSize({ allowCustom })} />
>
) : null}
update({ maxOutputs: maxOutputs || 1 })} />
updateQuality({ supported })} />
{profile.quality.supported ? (
) : null}
update({ transparentBackground })} />
update({ responseFormat: { supported } })} />
update({ outputFormat: { supported } })} />
);
}
return (
图片能力参数
生成界面和后端请求都会按此处裁剪参数
当前模型独立生效
updateReferences({ maxImages: maxImages || 0 })} />
updateReferences({ maxImageBytes: mbToBytes(maxImageBytes) })} />
updateReferences({ maskSupported })} />
update({ maxOutputs: maxOutputs || 1 })} />
{
const parameter = value as ImageCapabilityConfig["size"]["parameter"];
updateSize(
parameter === "none" ? { parameter, values: [], presets: undefined, default: "auto", allowCustom: false } : imageSizeConfigWithPresets({ ...profile, size: { ...profile.size, parameter } }, imageSizePresets(profile)),
);
}}
/>
{profile.size.parameter !== "none" ? (
<>
updateSize({ allowCustom })} />
>
) : null}
updateQuality({ supported })} />
{profile.quality.supported ? (
) : null}
update({ transparentBackground })} />
update({ responseFormat: { supported } })} />
update({ outputFormat: { supported } })} />
updateReferences({ promptMaxChars: promptMaxChars || 1 })} />
);
}
function CapabilityGroup({ title, description, children }: { title: string; description?: string; children: ReactNode }) {
return (
{title}
{description ? {description} : null}
{children}
);
}
function CapabilityBlock({ title, children }: { title: string; children: ReactNode }) {
return (
);
}
function ReferenceCard({ title, description, children }: { title: string; description: string; children: ReactNode }) {
return (
);
}
function ProtocolParameterCard({ step, title, description, children, className = "" }: { step: string; title: string; description: string; children: ReactNode; className?: string }) {
return (
);
}
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 (
);
}
function ParameterField({ label, description, supported, disabled, onChange }: { label: string; description: string; supported: boolean; disabled: boolean; onChange: (supported: boolean) => void }) {
return (
);
}
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);
}