340 lines
8.2 KiB
TypeScript
340 lines
8.2 KiB
TypeScript
import { inferKreaModelKind } from "@/lib/ai/krea-models";
|
|
|
|
const STATIC_VIDEO_PROVIDER_TYPES = new Set([
|
|
"bltcy-video",
|
|
"doubao-video",
|
|
"volcano-video",
|
|
"qianwen-video",
|
|
"fal-video",
|
|
"ruxa-video",
|
|
]);
|
|
|
|
const STATIC_IMAGE_PROVIDER_TYPES = new Set([
|
|
"gemini",
|
|
"huanke-gemini",
|
|
"alibaba-image",
|
|
"bfl-image",
|
|
"ruxa-image",
|
|
"fal-image",
|
|
"snapgen-image",
|
|
"grsai-gpt-image",
|
|
"openai-image",
|
|
"bananaimage",
|
|
"visionary",
|
|
"stepfun-image",
|
|
"midjourney",
|
|
"volcano-vision",
|
|
]);
|
|
|
|
const STATIC_3D_PROVIDER_TYPES = new Set([
|
|
"tripo-3d",
|
|
"worldlabs-3d",
|
|
"kittycad-cad",
|
|
]);
|
|
|
|
const RUNWAY_IMAGE_MODEL_IDS = new Set([
|
|
"gen4_image",
|
|
"gen4_image_turbo",
|
|
"gemini_2.5_flash",
|
|
]);
|
|
|
|
const RUNWAY_VIDEO_MODEL_PREFIXES = [
|
|
"gen4.5",
|
|
"gen4_turbo",
|
|
"gen4_aleph",
|
|
"gen3a_turbo",
|
|
"veo3.1_fast",
|
|
"veo3.1",
|
|
"veo3",
|
|
"act_two",
|
|
"upscale_v1",
|
|
];
|
|
|
|
const VIDEO_MODE_KEYS = new Set([
|
|
"text-to-video",
|
|
"image-to-video",
|
|
"video-to-video",
|
|
"audio-to-video",
|
|
"first-frame",
|
|
"first-last-frame",
|
|
]);
|
|
|
|
const VIDEO_KIND_KEYS = new Set([
|
|
"video",
|
|
"text-to-video",
|
|
"image-to-video",
|
|
"video-to-video",
|
|
"audio-to-video",
|
|
]);
|
|
|
|
const THREE_D_MODE_KEYS = new Set([
|
|
"3d",
|
|
"text-to-3d",
|
|
"text_to_3d",
|
|
"image-to-3d",
|
|
"image_to_3d",
|
|
"mesh-generation",
|
|
"mesh_generation",
|
|
"cad",
|
|
"world-generation",
|
|
"world_generation",
|
|
]);
|
|
|
|
const THREE_D_KIND_KEYS = new Set([
|
|
"3d",
|
|
"model-3d",
|
|
"model_3d",
|
|
"mesh",
|
|
"mesh-generation",
|
|
"mesh_generation",
|
|
"cad",
|
|
"world",
|
|
"world-generation",
|
|
"world_generation",
|
|
"3d-world",
|
|
"3d_world",
|
|
]);
|
|
|
|
const TEXT_TO_IMAGE_MODE_KEYS = new Set([
|
|
"text-to-image",
|
|
"text2image",
|
|
"txt2img",
|
|
"image-generation",
|
|
"image_generate",
|
|
"generate-image",
|
|
]);
|
|
|
|
const IMAGE_EDIT_ONLY_KIND_KEYS = new Set([
|
|
"image-edit",
|
|
"image-to-image",
|
|
"img2img",
|
|
"reference-image",
|
|
"reference-images",
|
|
]);
|
|
|
|
const TEXT_TO_IMAGE_KIND_KEYS = new Set([
|
|
"text-to-image",
|
|
"text2image",
|
|
"txt2img",
|
|
"image",
|
|
"image-generation",
|
|
"image_generate",
|
|
]);
|
|
|
|
function normalizeText(value?: string | null) {
|
|
return String(value || "").trim().toLowerCase();
|
|
}
|
|
|
|
export function isAlibabaImageTextToImageModelId(modelId?: string | null) {
|
|
const normalized = normalizeText(modelId);
|
|
return /^qwen-image-3(?:\.0)?(?:$|[-/])/.test(normalized);
|
|
}
|
|
|
|
function toRecord(value: unknown) {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
return null;
|
|
}
|
|
return value as Record<string, unknown>;
|
|
}
|
|
|
|
function getStringArrayField(source: unknown, key: string) {
|
|
const record = toRecord(source);
|
|
const value = record?.[key];
|
|
if (!Array.isArray(value)) return [] as string[];
|
|
return value.map((item) => normalizeText(String(item || ""))).filter(Boolean);
|
|
}
|
|
|
|
function getStringField(source: unknown, key: string) {
|
|
const record = toRecord(source);
|
|
return normalizeText(String(record?.[key] || ""));
|
|
}
|
|
|
|
function hasVideoModes(capabilities: unknown) {
|
|
if (!capabilities || typeof capabilities !== "object") return false;
|
|
const supportedModes = (capabilities as { supportedModes?: unknown }).supportedModes;
|
|
if (!Array.isArray(supportedModes)) return false;
|
|
return supportedModes.some((mode) => VIDEO_MODE_KEYS.has(String(mode || "").trim()));
|
|
}
|
|
|
|
function hasVideoKind(capabilities: unknown) {
|
|
return ["kind", "mediaType", "modelType", "outputType"].some((key) =>
|
|
VIDEO_KIND_KEYS.has(getStringField(capabilities, key))
|
|
);
|
|
}
|
|
|
|
function has3DModes(capabilities: unknown) {
|
|
const supportedModes = getStringArrayField(capabilities, "supportedModes");
|
|
return supportedModes.some((mode) => THREE_D_MODE_KEYS.has(mode));
|
|
}
|
|
|
|
function has3DKind(capabilities: unknown) {
|
|
return ["kind", "mediaType", "modelType", "outputType"].some((key) =>
|
|
THREE_D_KIND_KEYS.has(getStringField(capabilities, key))
|
|
);
|
|
}
|
|
|
|
function modelIdLooks3D(modelId?: string | null) {
|
|
const normalized = normalizeText(modelId);
|
|
if (!normalized) return false;
|
|
return (
|
|
/(?:^|[/_.-])(?:text|image)[_-]to[_-]3d(?:$|[/_.-])/.test(normalized) ||
|
|
/(?:^|[/_.-])(?:hunyuan3d|seed3d|tripo-h3(?:\.\d+)?)(?:$|[/_.-])/.test(
|
|
normalized
|
|
)
|
|
);
|
|
}
|
|
|
|
export function is3DModelDescriptor(params: {
|
|
providerType?: string | null;
|
|
modelId?: string | null;
|
|
capabilities?: unknown;
|
|
}): boolean {
|
|
const providerType = normalizeText(params.providerType);
|
|
return (
|
|
STATIC_3D_PROVIDER_TYPES.has(providerType) ||
|
|
has3DModes(params.capabilities) ||
|
|
has3DKind(params.capabilities) ||
|
|
modelIdLooks3D(params.modelId)
|
|
);
|
|
}
|
|
|
|
export function inferRunwayModelKind(modelId?: string | null): "image" | "video" | null {
|
|
const normalized = normalizeText(modelId);
|
|
if (!normalized) return null;
|
|
if (RUNWAY_IMAGE_MODEL_IDS.has(normalized)) return "image";
|
|
if (RUNWAY_VIDEO_MODEL_PREFIXES.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix}_`))) {
|
|
return "video";
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function isStaticVideoProviderType(providerType?: string | null): boolean {
|
|
return STATIC_VIDEO_PROVIDER_TYPES.has(normalizeText(providerType));
|
|
}
|
|
|
|
export function isVideoModelDescriptor(params: {
|
|
providerType?: string | null;
|
|
modelId?: string | null;
|
|
capabilities?: unknown;
|
|
supportsImageGen?: boolean | null;
|
|
}): boolean {
|
|
const providerType = normalizeText(params.providerType);
|
|
|
|
if (is3DModelDescriptor(params)) {
|
|
return false;
|
|
}
|
|
|
|
if (isStaticVideoProviderType(providerType)) {
|
|
return true;
|
|
}
|
|
|
|
if (hasVideoModes(params.capabilities) || hasVideoKind(params.capabilities)) {
|
|
return true;
|
|
}
|
|
|
|
if (providerType === "runway") {
|
|
return inferRunwayModelKind(params.modelId) === "video";
|
|
}
|
|
|
|
if (providerType === "krea") {
|
|
return inferKreaModelKind(params.modelId) === "video";
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
export function isImageGenerationModelDescriptor(params: {
|
|
providerType?: string | null;
|
|
modelId?: string | null;
|
|
capabilities?: unknown;
|
|
supportsImageGen?: boolean | null;
|
|
}): boolean {
|
|
const providerType = normalizeText(params.providerType);
|
|
|
|
if (is3DModelDescriptor(params)) {
|
|
return false;
|
|
}
|
|
|
|
if (providerType === "runway") {
|
|
const runwayModelKind = inferRunwayModelKind(params.modelId);
|
|
if (runwayModelKind === "image") return true;
|
|
if (runwayModelKind === "video") return false;
|
|
}
|
|
|
|
if (providerType === "krea") {
|
|
const kreaModelKind = inferKreaModelKind(params.modelId);
|
|
if (kreaModelKind === "image" || kreaModelKind === "image-enhance") return true;
|
|
if (kreaModelKind === "video") return false;
|
|
}
|
|
|
|
if (isVideoModelDescriptor(params)) {
|
|
return false;
|
|
}
|
|
|
|
if (params.supportsImageGen === true) {
|
|
return true;
|
|
}
|
|
|
|
return STATIC_IMAGE_PROVIDER_TYPES.has(providerType);
|
|
}
|
|
|
|
export function modelSupportsTextToImageDescriptor(params: {
|
|
providerType?: string | null;
|
|
modelId?: string | null;
|
|
capabilities?: unknown;
|
|
defaultParams?: unknown;
|
|
supportsImageGen?: boolean | null;
|
|
}): boolean {
|
|
if (
|
|
!isImageGenerationModelDescriptor({
|
|
providerType: params.providerType,
|
|
modelId: params.modelId,
|
|
capabilities: params.capabilities,
|
|
supportsImageGen: params.supportsImageGen,
|
|
})
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
const providerType = normalizeText(params.providerType);
|
|
const modelId = normalizeText(params.modelId);
|
|
const capabilityModes = getStringArrayField(params.capabilities, "supportedModes");
|
|
const defaultModes = getStringArrayField(params.defaultParams, "supportedModes");
|
|
const modes = new Set([...capabilityModes, ...defaultModes]);
|
|
|
|
if ([...modes].some((mode) => TEXT_TO_IMAGE_MODE_KEYS.has(mode))) {
|
|
return true;
|
|
}
|
|
|
|
const kind =
|
|
getStringField(params.capabilities, "kind") ||
|
|
getStringField(params.defaultParams, "kind");
|
|
if (TEXT_TO_IMAGE_KIND_KEYS.has(kind)) {
|
|
return true;
|
|
}
|
|
|
|
if (providerType === "stepfun-image") {
|
|
return true;
|
|
}
|
|
|
|
const runtimeEndpoint =
|
|
getStringField(params.capabilities, "endpoint") ||
|
|
getStringField(params.defaultParams, "endpoint");
|
|
const runtimeModelText = `${modelId} ${runtimeEndpoint}`.trim();
|
|
|
|
if (
|
|
IMAGE_EDIT_ONLY_KIND_KEYS.has(kind) ||
|
|
[...modes].some((mode) => IMAGE_EDIT_ONLY_KIND_KEYS.has(mode)) ||
|
|
runtimeModelText.endsWith("/edit") ||
|
|
runtimeModelText.includes("image-edit")
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (providerType === "alibaba-image") {
|
|
return isAlibabaImageTextToImageModelId(modelId);
|
|
}
|
|
|
|
return true;
|
|
}
|