107 lines
3.4 KiB
TypeScript
107 lines
3.4 KiB
TypeScript
type ModelDescriptor = {
|
|
displayName?: unknown;
|
|
modelId?: unknown;
|
|
supportsVision?: boolean | null;
|
|
capabilities?: unknown;
|
|
defaultParams?: unknown;
|
|
};
|
|
|
|
type ProviderDescriptor = {
|
|
type?: unknown;
|
|
};
|
|
|
|
const BFL_REFERENCE_IMAGE_MODEL_REGEX =
|
|
/^(?:bfl\/)?(?:flux-2-(?:max|flex|pro(?:-preview)?|klein-(?:4b|9b)(?:-preview)?)|flux-kontext-(?:pro|max))$/i;
|
|
const REFERENCE_IMAGE_MODE_REGEX =
|
|
/^(?:image-edit|image-to-image|img2img|reference-images?|image-reference|reference-image)$/i;
|
|
const GEMINI_TEXT_ONLY_IMAGE_MODEL_REGEX = /^(?:nano-banana-pro|nano-banana-2)$/i;
|
|
|
|
function readStringField(source: unknown, key: string) {
|
|
if (!source || typeof source !== "object" || !(key in source)) return "";
|
|
return String((source as Record<string, unknown>)[key] || "").trim();
|
|
}
|
|
|
|
function normalizeBflModelId(value: unknown) {
|
|
const trimmed = String(value || "").trim();
|
|
if (!trimmed) return "";
|
|
|
|
return trimmed
|
|
.replace(/^https?:\/\/[^/]+/i, "")
|
|
.replace(/^\/?(?:bfl\/)?v1\//i, "")
|
|
.replace(/^\/+/, "")
|
|
.replace(/[?#].*$/, "");
|
|
}
|
|
|
|
function readStringArrayField(source: unknown, key: string) {
|
|
if (!source || typeof source !== "object" || !(key in source)) return [];
|
|
const value = (source as Record<string, unknown>)[key];
|
|
if (!Array.isArray(value)) return [];
|
|
return value.map((item) => String(item || "").trim()).filter(Boolean);
|
|
}
|
|
|
|
function hasReferenceImageMode(value: unknown) {
|
|
if (!value || typeof value !== "object") return false;
|
|
const supportedModes = [
|
|
...readStringArrayField(value, "supportedModes"),
|
|
...readStringArrayField(value, "modes"),
|
|
];
|
|
return supportedModes.some((mode) => REFERENCE_IMAGE_MODE_REGEX.test(mode));
|
|
}
|
|
|
|
export function isKnownReferenceImageModel(
|
|
model: ModelDescriptor | null | undefined,
|
|
provider?: ProviderDescriptor | null
|
|
) {
|
|
const providerType = String(provider?.type || "").trim();
|
|
const candidates = [
|
|
model?.displayName,
|
|
model?.modelId,
|
|
readStringField(model?.defaultParams, "endpoint"),
|
|
readStringField(model?.defaultParams, "submitEndpoint"),
|
|
readStringField(model?.capabilities, "endpoint"),
|
|
];
|
|
|
|
const capabilitiesEndpoints =
|
|
model?.capabilities &&
|
|
typeof model.capabilities === "object" &&
|
|
"endpoints" in model.capabilities
|
|
? (model.capabilities as { endpoints?: unknown }).endpoints
|
|
: undefined;
|
|
|
|
candidates.push(readStringField(capabilitiesEndpoints, "submit"));
|
|
|
|
const matchesKnownBflModel = candidates
|
|
.map(normalizeBflModelId)
|
|
.some((modelId) => BFL_REFERENCE_IMAGE_MODEL_REGEX.test(modelId));
|
|
if (matchesKnownBflModel) return true;
|
|
|
|
if (providerType === "bfl-image") {
|
|
return hasReferenceImageMode(model?.capabilities) || hasReferenceImageMode(model?.defaultParams);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
export function modelLacksReferenceImageInputSupport(
|
|
model: ModelDescriptor | null | undefined,
|
|
provider?: ProviderDescriptor | null
|
|
) {
|
|
if (isKnownReferenceImageModel(model, provider)) return false;
|
|
const providerType = String(provider?.type || "").trim().toLowerCase();
|
|
const modelId = String(model?.modelId || "").trim();
|
|
if (
|
|
providerType === "gemini" &&
|
|
GEMINI_TEXT_ONLY_IMAGE_MODEL_REGEX.test(modelId)
|
|
) {
|
|
return true;
|
|
}
|
|
return model?.supportsVision === false;
|
|
}
|
|
|
|
export function modelSupportsReferenceImageInput(
|
|
model: ModelDescriptor | null | undefined,
|
|
provider?: ProviderDescriptor | null
|
|
) {
|
|
return !modelLacksReferenceImageInputSupport(model, provider);
|
|
}
|