feat(*): 模型能力配置 - 支持模型级视频限制校验与协议管理

This commit is contained in:
ddCat
2026-08-06 15:34:45 +08:00
parent a18673a101
commit 9ca9dd9df6
36 changed files with 1692 additions and 401 deletions

View File

@@ -0,0 +1,121 @@
import type { ModelProtocol } from "@/lib/model-protocols";
export type ModelCapabilityConfig = {
version: number;
video?: VideoCapabilityConfig;
};
export type VideoCapabilityConfig = {
references: {
promptMaxChars: number;
maxImages: number;
maxImageBytes: number;
maxVideos: number;
maxVideoBytes: number;
maxVideoDurationSeconds: number;
maxAudios: number;
maxAudioBytes: number;
maxAudioDurationSeconds: number;
};
duration: {
selection: "range" | "enum";
min?: number;
max?: number;
step?: number;
values?: number[];
default: number;
};
ratios: string[];
defaultRatio: string;
resolutions: string[];
defaultResolution: string;
generateAudio: { supported: boolean; default: boolean };
watermark: { supported: boolean; default: boolean };
operations: string[];
defaultOperation: string;
};
export function defaultModelCapabilityConfig(protocol?: ModelProtocol): ModelCapabilityConfig {
const video: VideoCapabilityConfig = {
references: {
promptMaxChars: 1000,
maxImages: 9,
maxImageBytes: 30 * 1024 * 1024,
maxVideos: 0,
maxVideoBytes: 0,
maxVideoDurationSeconds: 0,
maxAudios: 0,
maxAudioBytes: 0,
maxAudioDurationSeconds: 0,
},
duration: { selection: "range", min: 1, max: 15, step: 1, default: 6 },
ratios: ["16:9", "9:16", "1:1", "4:3", "3:4", "21:9"],
defaultRatio: "16:9",
resolutions: ["480p", "720p", "1080p", "2160p"],
defaultResolution: "720p",
generateAudio: { supported: false, default: false },
watermark: { supported: false, default: false },
operations: ["text_to_video", "image_to_video"],
defaultOperation: "text_to_video",
};
if (protocol === "volcengine-jimeng-video") video.duration = { selection: "enum", values: [5, 10], default: 5 };
if (protocol === "gemini-veo") {
video.duration = { selection: "enum", values: [4, 6, 8], default: 6 };
video.resolutions = ["720p", "1080p"];
}
if (protocol === "volcengine-ark-video" || protocol === "newapi-channel-1" || protocol === "newapi-channel-2") {
video.references.maxVideos = 3;
video.references.maxAudios = 3;
video.references.maxVideoBytes = 200 * 1024 * 1024;
video.references.maxAudioBytes = 15 * 1024 * 1024;
video.references.maxVideoDurationSeconds = 15;
video.references.maxAudioDurationSeconds = 15;
video.generateAudio = { supported: true, default: true };
}
if (protocol === "volcengine-ark-video") video.watermark = { supported: true, default: false };
return { version: 1, video };
}
export function modelCapabilityConfigFor(config: { channels: Array<{ id: string; models: string[]; modelCosts?: Array<{ model: string; capabilityConfig?: ModelCapabilityConfig; protocol?: ModelProtocol }> }> }, model: string) {
const separator = model.indexOf("::");
const channelId = separator >= 0 ? model.slice(0, separator) : "";
const modelName = separator >= 0 ? model.slice(separator + 2) : model;
const channel = config.channels.find((item) => item.id === channelId) || config.channels.find((item) => item.models.includes(modelName));
const cost = channel?.modelCosts?.find((item) => item.model === modelName);
return cost?.capabilityConfig || defaultModelCapabilityConfig(cost?.protocol);
}
export function normalizeVideoValue(profile: VideoCapabilityConfig, value: { seconds?: string; ratio?: string; resolution?: string }) {
const duration = profile.duration.selection === "enum"
? (profile.duration.values || []).includes(Number(value.seconds)) ? Number(value.seconds) : profile.duration.default
: normalizeRangeDuration(profile, Number(value.seconds));
const ratio = profile.ratios.includes(value.ratio || "") ? value.ratio! : profile.defaultRatio;
const resolution = profile.resolutions.includes(value.resolution || "") ? value.resolution! : profile.defaultResolution;
return { seconds: String(duration), ratio, resolution };
}
function normalizeRangeDuration(profile: VideoCapabilityConfig, value: number) {
const min = profile.duration.min || 1;
const max = profile.duration.max || min;
const step = profile.duration.step || 1;
const candidate = Number.isFinite(value) ? Math.floor(value) : profile.duration.default;
const clamped = Math.min(max, Math.max(min, candidate));
const maxStep = Math.max(0, Math.floor((max - min) / step));
return min + Math.min(maxStep, Math.max(0, Math.round((clamped - min) / step))) * step;
}
export function videoDurationOptions(profile: VideoCapabilityConfig) {
if (profile.duration.selection === "enum") return profile.duration.values || [];
const min = profile.duration.min || 1;
const max = profile.duration.max || min;
const step = profile.duration.step || 1;
return Array.from({ length: Math.floor((max - min) / step) + 1 }, (_, index) => min + index * step);
}
export function videoDurationAllowed(profile: VideoCapabilityConfig, value: number) {
if (profile.duration.selection === "enum") return (profile.duration.values || []).includes(value);
const min = profile.duration.min || 1;
const max = profile.duration.max || min;
const step = profile.duration.step || 1;
return value >= min && value <= max && (value - min) % step === 0;
}