mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 21:02:12 +03:00
Update the main and localized changelogs with the 3.8.7 release notes, including new API self-service status, analytics retention, RAM optimizations, and token accounting fixes. Clarify release skill instructions to ensure new release branches are always created from the latest main branch.
697 lines
24 KiB
TypeScript
697 lines
24 KiB
TypeScript
/**
|
|
* Image Generation Provider Registry
|
|
*
|
|
* Defines providers that support the /v1/images/generations endpoint.
|
|
* Each provider has its own request format and endpoint.
|
|
*/
|
|
|
|
interface ImageModelEntry {
|
|
id: string;
|
|
name: string;
|
|
inputModalities?: string[];
|
|
description?: string;
|
|
isMarket?: boolean;
|
|
}
|
|
|
|
interface ImageProviderConfig {
|
|
id: string;
|
|
baseUrl: string;
|
|
fallbackUrl?: string;
|
|
proUrl?: string;
|
|
statusUrl?: string;
|
|
alias?: string;
|
|
authType: string;
|
|
authHeader: string;
|
|
format: string;
|
|
models: ImageModelEntry[];
|
|
supportedSizes: string[];
|
|
}
|
|
|
|
interface ImageModelAliasEntry {
|
|
provider: string;
|
|
model: string;
|
|
name: string;
|
|
listInCatalog: boolean;
|
|
inputModalities?: string[];
|
|
description?: string;
|
|
}
|
|
|
|
const IMAGE_MODEL_ALIASES: Record<string, ImageModelAliasEntry> = {
|
|
"gemini-3.1-flash-image-preview": {
|
|
provider: "antigravity",
|
|
model: "gemini-3.1-flash-image",
|
|
name: "Gemini 3.1 Flash Image",
|
|
listInCatalog: false,
|
|
},
|
|
"flux-kontext": {
|
|
provider: "black-forest-labs",
|
|
model: "flux-kontext-pro",
|
|
name: "FLUX Kontext Pro",
|
|
listInCatalog: true,
|
|
inputModalities: ["text", "image"],
|
|
},
|
|
"flux-kontext-max": {
|
|
provider: "black-forest-labs",
|
|
model: "flux-kontext-max",
|
|
name: "FLUX Kontext Max",
|
|
listInCatalog: true,
|
|
inputModalities: ["text", "image"],
|
|
},
|
|
"flux-2-max": {
|
|
provider: "black-forest-labs",
|
|
model: "flux-2-max",
|
|
name: "FLUX.2 Max",
|
|
listInCatalog: true,
|
|
inputModalities: ["text", "image"],
|
|
},
|
|
"flux-2-pro": {
|
|
provider: "black-forest-labs",
|
|
model: "flux-2-pro",
|
|
name: "FLUX.2 Pro",
|
|
listInCatalog: true,
|
|
inputModalities: ["text", "image"],
|
|
},
|
|
"flux-2-flex": {
|
|
provider: "black-forest-labs",
|
|
model: "flux-2-flex",
|
|
name: "FLUX.2 Flex",
|
|
listInCatalog: true,
|
|
inputModalities: ["text", "image"],
|
|
},
|
|
"flux-2-dev": {
|
|
provider: "together",
|
|
model: "black-forest-labs/FLUX.2-dev",
|
|
name: "FLUX.2 Dev",
|
|
listInCatalog: true,
|
|
inputModalities: ["text", "image"],
|
|
},
|
|
kontext: {
|
|
provider: "black-forest-labs",
|
|
model: "flux-kontext-pro",
|
|
name: "FLUX Kontext Pro",
|
|
listInCatalog: false,
|
|
inputModalities: ["text", "image"],
|
|
},
|
|
"pollinations/kontext": {
|
|
provider: "black-forest-labs",
|
|
model: "flux-kontext-pro",
|
|
name: "FLUX Kontext Pro",
|
|
listInCatalog: false,
|
|
inputModalities: ["text", "image"],
|
|
},
|
|
};
|
|
|
|
function resolveImageModelAlias(modelStr) {
|
|
const alias = IMAGE_MODEL_ALIASES[modelStr];
|
|
return alias ? { provider: alias.provider, model: alias.model } : null;
|
|
}
|
|
|
|
function findImageModelConfig(providerId, modelId) {
|
|
const provider = IMAGE_PROVIDERS[providerId];
|
|
if (!provider) return null;
|
|
return provider.models.find((model) => model.id === modelId) || null;
|
|
}
|
|
|
|
let _IMAGE_PROVIDERS: Record<string, ImageProviderConfig> | null = null;
|
|
|
|
function getOrCreateImageProviders(): Record<string, ImageProviderConfig> {
|
|
if (!_IMAGE_PROVIDERS) {
|
|
_IMAGE_PROVIDERS = {
|
|
openai: {
|
|
id: "openai",
|
|
baseUrl: "https://api.openai.com/v1/images/generations",
|
|
authType: "apikey",
|
|
authHeader: "bearer",
|
|
format: "openai", // native OpenAI format
|
|
models: [
|
|
{ id: "gpt-image-2", name: "GPT Image 2" },
|
|
{ id: "gpt-image-1.5", name: "GPT Image 1.5" },
|
|
{ id: "gpt-image-1-mini", name: "GPT Image 1 Mini" },
|
|
],
|
|
supportedSizes: ["1024x1024", "1024x1792", "1792x1024"],
|
|
},
|
|
|
|
// Codex exposes image generation only as a Responses-API hosted tool under
|
|
// ChatGPT OAuth. Incoming GPT-Image-style `/v1/images/generations` requests are
|
|
// translated to /responses calls with `tools: [{ type: "image_generation" }]`
|
|
// by handleCodexImageGeneration.
|
|
codex: {
|
|
id: "codex",
|
|
alias: "cx",
|
|
baseUrl: "https://chatgpt.com/backend-api/codex/responses",
|
|
authType: "oauth",
|
|
authHeader: "bearer",
|
|
format: "codex-responses",
|
|
models: [{ id: "gpt-5.5", name: "GPT 5.5 (Codex Image)" }],
|
|
supportedSizes: ["1024x1024", "1024x1536", "1536x1024"],
|
|
},
|
|
|
|
"chatgpt-web": {
|
|
id: "chatgpt-web",
|
|
alias: "cgpt-web",
|
|
baseUrl: "https://chatgpt.com/backend-api/f/conversation",
|
|
authType: "apikey",
|
|
authHeader: "cookie",
|
|
format: "chatgpt-web",
|
|
models: [{ id: "gpt-5.3-instant", name: "GPT-5.3 Instant (ChatGPT Web Image)" }],
|
|
supportedSizes: ["1024x1024", "1024x1536", "1536x1024"],
|
|
},
|
|
|
|
xai: {
|
|
id: "xai",
|
|
baseUrl: "https://api.x.ai/v1/images/generations",
|
|
authType: "apikey",
|
|
authHeader: "bearer",
|
|
format: "openai",
|
|
models: [
|
|
{ id: "grok-imagine-image-quality", name: "Grok Imagine Image Quality" },
|
|
{ id: "grok-imagine-image", name: "Grok Imagine Image" },
|
|
],
|
|
supportedSizes: ["1024x1024", "2048x2048"],
|
|
},
|
|
|
|
together: {
|
|
id: "together",
|
|
baseUrl: "https://api.together.xyz/v1/images/generations",
|
|
authType: "apikey",
|
|
authHeader: "bearer",
|
|
format: "openai",
|
|
models: [
|
|
{
|
|
id: "black-forest-labs/FLUX.2-max",
|
|
name: "FLUX.2 Max",
|
|
inputModalities: ["text", "image"],
|
|
},
|
|
{
|
|
id: "black-forest-labs/FLUX.2-pro",
|
|
name: "FLUX.2 Pro",
|
|
inputModalities: ["text", "image"],
|
|
},
|
|
{
|
|
id: "black-forest-labs/FLUX.2-flex",
|
|
name: "FLUX.2 Flex",
|
|
inputModalities: ["text", "image"],
|
|
},
|
|
{
|
|
id: "black-forest-labs/FLUX.2-dev",
|
|
name: "FLUX.2 Dev",
|
|
inputModalities: ["text", "image"],
|
|
},
|
|
{ id: "openai/gpt-image-1.5", name: "GPT Image 1.5", inputModalities: ["text", "image"] },
|
|
{ id: "Wan-AI/Wan2.6-image", name: "Wan 2.6 Image", inputModalities: ["text", "image"] },
|
|
{
|
|
id: "Qwen/Qwen-Image-2.0-Pro",
|
|
name: "Qwen Image 2.0 Pro",
|
|
inputModalities: ["text", "image"],
|
|
},
|
|
{ id: "Qwen/Qwen-Image-2.0", name: "Qwen Image 2.0", inputModalities: ["text", "image"] },
|
|
{ id: "google/flash-image-3.1", name: "NanoBanana 2", inputModalities: ["text", "image"] },
|
|
{
|
|
id: "google/gemini-3-pro-image",
|
|
name: "NanoBanana Pro",
|
|
inputModalities: ["text", "image"],
|
|
},
|
|
],
|
|
supportedSizes: ["1024x1024", "512x512"],
|
|
},
|
|
|
|
fireworks: {
|
|
id: "fireworks",
|
|
baseUrl: "https://api.fireworks.ai/inference/v1/images/generations",
|
|
authType: "apikey",
|
|
authHeader: "bearer",
|
|
format: "openai",
|
|
models: [
|
|
{ id: "accounts/fireworks/flux-kontext-max", name: "FLUX Kontext Max" },
|
|
{ id: "accounts/fireworks/flux-kontext-pro", name: "FLUX Kontext Pro" },
|
|
{ id: "accounts/fireworks/flux-1-schnell-fp8", name: "FLUX.1 schnell" },
|
|
{ id: "accounts/fireworks/models/flux-1-dev-fp8", name: "FLUX 1 Dev FP8" },
|
|
{ id: "accounts/fireworks/models/stable-diffusion-xl-1024-v1-0", name: "SDXL 1024 v1.0" },
|
|
],
|
|
supportedSizes: ["1024x1024", "512x512"],
|
|
},
|
|
|
|
antigravity: {
|
|
id: "antigravity",
|
|
baseUrl: "https://daily-cloudcode-pa.googleapis.com/v1internal:generateContent",
|
|
authType: "oauth",
|
|
authHeader: "bearer",
|
|
format: "gemini-image", // Special format: uses Gemini generateContent API
|
|
models: [{ id: "gemini-3.1-flash-image", name: "Gemini 3.1 Flash Image" }],
|
|
supportedSizes: ["1024x1024"],
|
|
},
|
|
|
|
//Curruntly no models serving
|
|
nebius: {
|
|
id: "nebius",
|
|
baseUrl: "https://api.tokenfactory.nebius.com/v1/images/generations",
|
|
fallbackUrl: "https://api.studio.nebius.com/v1/images/generations",
|
|
authType: "apikey",
|
|
authHeader: "bearer",
|
|
format: "openai",
|
|
models: [{ id: "black-forest-labs/flux-schnell", name: "No Model yet" }],
|
|
supportedSizes: ["1024x1024"],
|
|
},
|
|
|
|
hyperbolic: {
|
|
id: "hyperbolic",
|
|
baseUrl: "https://api.hyperbolic.xyz/v1/image/generation",
|
|
authType: "apikey",
|
|
authHeader: "bearer",
|
|
format: "hyperbolic", // custom: uses model_name, returns base64 images
|
|
models: [{ id: "SDXL1.0-base", name: "No Model yet" }],
|
|
supportedSizes: ["1024x1024"],
|
|
},
|
|
//Curruntly no models serving
|
|
|
|
nanobanana: {
|
|
id: "nanobanana",
|
|
baseUrl: "https://api.nanobananaapi.ai/api/v1/nanobanana/generate",
|
|
proUrl: "https://api.nanobananaapi.ai/api/v1/nanobanana/generate-pro",
|
|
statusUrl: "https://api.nanobananaapi.ai/api/v1/nanobanana/record-info",
|
|
authType: "apikey",
|
|
authHeader: "bearer",
|
|
format: "nanobanana", // custom format (async: submit task, then poll)
|
|
models: [
|
|
{ id: "nanobanana-flash", name: "NanoBanana Flash (Gemini 2.5 Flash)" },
|
|
{ id: "nanobanana-pro", name: "NanoBanana Pro (Gemini 3 Pro)" },
|
|
],
|
|
supportedSizes: ["1024x1024", "1024x1280", "1024x1536", "1536x1024", "1280x1024"],
|
|
},
|
|
|
|
kie: {
|
|
id: "kie",
|
|
baseUrl: "https://api.kie.ai",
|
|
statusUrl: "https://api.kie.ai/api/v1/jobs/recordInfo",
|
|
authType: "apikey",
|
|
authHeader: "bearer",
|
|
format: "kie-image",
|
|
models: [
|
|
{ id: "gpt4o-image", name: "KIE 4o Image" },
|
|
{ id: "seedream/4.5-text-to-image", name: "Seedream 4.5", isMarket: true },
|
|
{ id: "seedream/4.5-edit", name: "Seedream 4.5 Edit", isMarket: true },
|
|
{ id: "seedream/5.0-lite-text-to-image", name: "Seedream 5.0 Lite", isMarket: true },
|
|
{ id: "seedream/5.0-lite-image-to-image", name: "Seedream 5.0 Lite I2I", isMarket: true },
|
|
{ id: "z-image/4.0-text-to-image", name: "Z-Image v4.0", isMarket: true },
|
|
{ id: "z-image/4.5-text-to-image", name: "Z-Image v4.5", isMarket: true },
|
|
{ id: "google-imagen/imagen4-fast", name: "Imagen 4 Fast", isMarket: true },
|
|
{ id: "google-imagen/imagen4-ultra", name: "Imagen 4 Ultra", isMarket: true },
|
|
{ id: "google-imagen/imagen4", name: "Imagen 4", isMarket: true },
|
|
{ id: "google-imagen/nano-banana-2", name: "Nano Banana 2", isMarket: true },
|
|
{ id: "google-imagen/nano-banana", name: "Nano Banana", isMarket: true },
|
|
{ id: "google-imagen/nano-banana-pro", name: "Nano Banana Pro", isMarket: true },
|
|
{ id: "google-imagen/nano-banana-edit", name: "Nano Banana Edit", isMarket: true },
|
|
{ id: "flux/2-pro-image-to-image", name: "Flux 2 Pro I2I", isMarket: true },
|
|
{ id: "flux/2-pro-text-to-image", name: "Flux 2 Pro T2I", isMarket: true },
|
|
{ id: "flux/2-image-to-image", name: "Flux 2 I2I", isMarket: true },
|
|
{ id: "flux/2-text-to-image", name: "Flux 2 T2I", isMarket: true },
|
|
{ id: "flux/kontext", name: "Flux Kontext", isMarket: true },
|
|
{ id: "grok-imagine/text-to-image", name: "Grok Imagine T2I", isMarket: true },
|
|
{ id: "grok-imagine/image-to-image", name: "Grok Imagine I2I", isMarket: true },
|
|
{ id: "gpt/gpt-image-1.5-text-to-image", name: "GPT Image 1.5 T2I", isMarket: true },
|
|
{ id: "gpt/gpt-image-1.5-image-to-image", name: "GPT Image 1.5 I2I", isMarket: true },
|
|
{ id: "gpt/gpt-image-2-text-to-image", name: "GPT Image 2 T2I", isMarket: true },
|
|
{ id: "gpt/gpt-image-2-image-to-image", name: "GPT Image 2 I2I", isMarket: true },
|
|
{ id: "ideogram/v3-text-to-image", name: "Ideogram v3", isMarket: true },
|
|
{ id: "ideogram/v3-edit", name: "Ideogram v3 Edit", isMarket: true },
|
|
{ id: "ideogram/v3-remix", name: "Ideogram v3 Remix", isMarket: true },
|
|
{ id: "ideogram/v3-reframe", name: "Ideogram v3 Reframe", isMarket: true },
|
|
{ id: "qwen/text-to-image", name: "Qwen T2I", isMarket: true },
|
|
{ id: "qwen/image-to-image", name: "Qwen I2I", isMarket: true },
|
|
{ id: "qwen/image-edit", name: "Qwen Edit", isMarket: true },
|
|
{ id: "qwen2/image-edit", name: "Qwen2 Edit", isMarket: true },
|
|
{ id: "qwen2/text-to-image", name: "Qwen2 T2I", isMarket: true },
|
|
{ id: "wan/2.7-image", name: "Wan 2.7 Image", isMarket: true },
|
|
{ id: "wan/2.7-image-pro", name: "Wan 2.7 Image Pro", isMarket: true },
|
|
],
|
|
supportedSizes: ["1:1", "16:9", "9:16", "4:3", "3:4"],
|
|
},
|
|
|
|
haiper: {
|
|
id: "haiper",
|
|
baseUrl: "https://api.haiper.ai/v1/jobs/gen2/text2image",
|
|
statusUrl: "https://api.haiper.ai/v1/jobs",
|
|
authType: "apikey",
|
|
authHeader: "HAIPER_KEY",
|
|
format: "haiper-image",
|
|
models: [{ id: "gen2", name: "Gen 2 Image" }],
|
|
supportedSizes: ["16:9", "9:16", "1:1", "4:3", "3:4"],
|
|
},
|
|
leonardo: {
|
|
id: "leonardo",
|
|
baseUrl: "https://cloud.leonardo.ai/api/rest/v1/generations",
|
|
authType: "apikey",
|
|
authHeader: "bearer",
|
|
format: "leonardo-image",
|
|
models: [
|
|
{ id: "phoenix", name: "Phoenix" },
|
|
{ id: "sdxl", name: "SDXL" },
|
|
],
|
|
supportedSizes: ["1024x1024", "1024x576", "576x1024"],
|
|
},
|
|
ideogram: {
|
|
id: "ideogram",
|
|
baseUrl: "https://api.ideogram.ai/generate",
|
|
authType: "apikey",
|
|
authHeader: "Api-Key",
|
|
format: "ideogram-image",
|
|
models: [
|
|
{ id: "V_3", name: "Ideogram V3" },
|
|
{ id: "V_2A", name: "Ideogram V2A" },
|
|
],
|
|
supportedSizes: ["1024x1024", "1024x1792", "1792x1024"],
|
|
},
|
|
sdwebui: {
|
|
id: "sdwebui",
|
|
baseUrl: "http://localhost:7860/sdapi/v1/txt2img",
|
|
authType: "none",
|
|
authHeader: "none",
|
|
format: "sdwebui",
|
|
models: [
|
|
{ id: "stable-diffusion-v1-5", name: "Stable Diffusion v1.5" },
|
|
{ id: "sdxl-base-1.0", name: "SDXL Base 1.0" },
|
|
],
|
|
supportedSizes: ["512x512", "768x768", "1024x1024"],
|
|
},
|
|
|
|
comfyui: {
|
|
id: "comfyui",
|
|
baseUrl: "http://localhost:8188",
|
|
authType: "none",
|
|
authHeader: "none",
|
|
format: "comfyui",
|
|
models: [
|
|
{ id: "flux-dev", name: "FLUX Dev" },
|
|
{ id: "sdxl", name: "SDXL" },
|
|
],
|
|
supportedSizes: ["512x512", "768x768", "1024x1024"],
|
|
},
|
|
|
|
openrouter: {
|
|
id: "openrouter",
|
|
baseUrl: "https://openrouter.ai/api/v1/images/generations",
|
|
authType: "apikey",
|
|
authHeader: "bearer",
|
|
format: "openai",
|
|
models: [
|
|
{ id: "openai/gpt-5.4-image-2", name: "GPT Image 2 (via OpenRouter)" },
|
|
{ id: "openai/gpt-5-image-mini", name: "GPT Image 1 Mini (via OpenRouter)" },
|
|
{ id: "google/gemini-3.1-flash-image-preview", name: "Nano Banana 2 (via OpenRouter)" },
|
|
{ id: "google/gemini-3-pro-image-preview", name: "Nano Banana Pro (via OpenRouter)" },
|
|
{ id: "black-forest-labs/flux.2-max", name: "FLUX.2 Max (via OpenRouter)" },
|
|
{ id: "black-forest-labs/flux.2-pro", name: "FLUX.2 Pro (via OpenRouter)" },
|
|
{ id: "black-forest-labs/flux.2-flex", name: "FLUX.2 Flex (via OpenRouter)" },
|
|
],
|
|
supportedSizes: ["1024x1024", "1024x1792", "1792x1024"],
|
|
},
|
|
|
|
pollinations: {
|
|
id: "pollinations",
|
|
alias: "pol",
|
|
baseUrl: "https://gen.pollinations.ai/v1/images/generations",
|
|
authType: "apikey",
|
|
authHeader: "bearer",
|
|
format: "openai",
|
|
models: [
|
|
{ id: "klein", name: "FLUX.2 Klein 4B" },
|
|
{ id: "flux", name: "Flux Schnell" },
|
|
{ id: "zimage", name: "Z-Image Turbo" },
|
|
{ id: "qwen-image", name: "Qwen Image Plus" },
|
|
{ id: "wan-image", name: "Wan 2.7 Image" },
|
|
{ id: "gpt-image-2", name: "GPT Image 2" },
|
|
{ id: "gptimage-large", name: "GPT Image 1.5" },
|
|
{ id: "gptimage", name: "GPT Image 1 Mini" },
|
|
],
|
|
supportedSizes: ["1024x1024", "512x512"],
|
|
},
|
|
|
|
"fal-ai": {
|
|
id: "fal-ai",
|
|
baseUrl: "https://fal.run",
|
|
authType: "apikey",
|
|
authHeader: "key",
|
|
format: "fal-ai",
|
|
models: [
|
|
{ id: "fal-ai/flux-2-max", name: "FLUX.2 Max" },
|
|
{ id: "fal-ai/flux-2-pro", name: "FLUX.2 Pro" },
|
|
{ id: "fal-ai/flux-2-flex", name: "FLUX.2 Flex" },
|
|
{ id: "bria/text-to-image/3.2", name: "Bria 3.2" },
|
|
{ id: "fal-ai/bytedance/seedream/v4.5/text-to-image", name: "SeeDream V4.5" },
|
|
{ id: "fal-ai/bytedance/dreamina/v3.1/text-to-image", name: "Dreamina V3.1" },
|
|
{ id: "fal-ai/ideogram/v3", name: "Ideogram V3" },
|
|
{ id: "fal-ai/nano-banana-pro", name: "Nano Banana Pro" },
|
|
{ id: "fal-ai/nano-banana-2", name: "Nano Banana 2" },
|
|
{ id: "fal-ai/recraft/v4/pro/text-to-image", name: "Recraft V4 Pro via Fal" },
|
|
{ id: "fal-ai/recraft/v4/text-to-image", name: "Recraft V4 via Fal" },
|
|
{ id: "fal-ai/stable-diffusion-v35-medium", name: "Stable Diffusion v3.5 Medium" },
|
|
],
|
|
supportedSizes: ["1024x1024", "1024x1280", "1280x1024"],
|
|
},
|
|
|
|
"stability-ai": {
|
|
id: "stability-ai",
|
|
baseUrl: "https://api.stability.ai",
|
|
authType: "apikey",
|
|
authHeader: "bearer",
|
|
format: "stability-ai",
|
|
models: [
|
|
{ id: "stable-image-ultra", name: "Stable Image Ultra" },
|
|
{ id: "stable-image-core", name: "Stable Image Core" },
|
|
{ id: "sd3.5-large-turbo", name: "sd3.5-large-turbo" },
|
|
{ id: "sd3.5-large", name: "sd3.5-large" },
|
|
{ id: "sd3.5-medium", name: "sd3.5-medium" },
|
|
{ id: "sd3.5-flash", name: "sd3.5-flash" },
|
|
{ id: "erase", name: "Erase", inputModalities: ["image"] },
|
|
{ id: "inpaint", name: "Inpaint", inputModalities: ["text", "image"] },
|
|
{ id: "outpaint", name: "Outpaint", inputModalities: ["text", "image"] },
|
|
{ id: "remove-background", name: "Remove Background", inputModalities: ["image"] },
|
|
{ id: "search-and-replace", name: "Search and Replace", inputModalities: ["text", "image"] },
|
|
{ id: "search-and-recolor", name: "Search and Recolor", inputModalities: ["text", "image"] },
|
|
{
|
|
id: "replace-background-and-relight",
|
|
name: "Replace Background and Relight",
|
|
inputModalities: ["text", "image"],
|
|
},
|
|
{ id: "creative", name: "Creative Upscale", inputModalities: ["text", "image"] },
|
|
{ id: "fast", name: "Fast Upscale", inputModalities: ["image"] },
|
|
{ id: "conservative", name: "Conservative Upscale", inputModalities: ["image"] },
|
|
{ id: "sketch", name: "Sketch Control", inputModalities: ["text", "image"] },
|
|
{ id: "structure", name: "Structure Control", inputModalities: ["text", "image"] },
|
|
{ id: "style", name: "Style Control", inputModalities: ["text", "image"] },
|
|
{ id: "style-transfer", name: "Style Transfer", inputModalities: ["text", "image"] },
|
|
],
|
|
supportedSizes: ["1024x1024", "1024x1280", "1280x1024"],
|
|
},
|
|
|
|
"black-forest-labs": {
|
|
id: "black-forest-labs",
|
|
baseUrl: "https://api.bfl.ai",
|
|
authType: "apikey",
|
|
authHeader: "x-key",
|
|
format: "black-forest-labs",
|
|
models: [
|
|
{ id: "flux-2-max", name: "FLUX.2 Max" },
|
|
{ id: "flux-2-pro", name: "FLUX.2 Pro" },
|
|
{ id: "flux-2-flex", name: "FLUX.2 Flex" },
|
|
{ id: "flux-pro-1.1-ultra", name: "flux-pro-1.1-ultra" },
|
|
{ id: "flux-pro-1.1", name: "flux-pro-1.1" },
|
|
{ id: "flux-2-klein-9b", name: "flux 2 Klein 9B" },
|
|
{ id: "flux-2-klein-4b", name: "flux 2 Klein 4B" },
|
|
{ id: "flux-kontext-max", name: "flux-kontext-max", inputModalities: ["text", "image"] },
|
|
{ id: "flux-kontext-pro", name: "flux-kontext-pro", inputModalities: ["text", "image"] },
|
|
{ id: "flux-dev", name: "flux-dev" },
|
|
{ id: "flux-pro", name: "flux-pro" },
|
|
],
|
|
supportedSizes: ["1024x1024", "1024x1280", "1280x1024"],
|
|
},
|
|
|
|
recraft: {
|
|
id: "recraft",
|
|
baseUrl: "https://external.api.recraft.ai",
|
|
authType: "apikey",
|
|
authHeader: "bearer",
|
|
format: "recraft",
|
|
models: [
|
|
{ id: "recraftv4_pro", name: "Recraft V4 Pro" },
|
|
{ id: "recraftv4", name: "Recraft V4" },
|
|
{ id: "recraftv3", name: "Recraft V3" },
|
|
{ id: "recraftv2", name: "Recraft V2" },
|
|
],
|
|
supportedSizes: ["1024x1024", "1024x1280", "1280x1024"],
|
|
},
|
|
|
|
topaz: {
|
|
id: "topaz",
|
|
baseUrl: "https://api.topazlabs.com",
|
|
authType: "apikey",
|
|
authHeader: "x-api-key",
|
|
format: "topaz",
|
|
models: [{ id: "topaz-enhance", name: "topaz-enhance", inputModalities: ["image"] }],
|
|
supportedSizes: ["1024x1024"],
|
|
},
|
|
nanogpt: {
|
|
id: "nanogpt",
|
|
baseUrl: "https://nano-gpt.com/api/v1/images/generations",
|
|
authType: "apikey",
|
|
authHeader: "bearer",
|
|
format: "openai",
|
|
models: [
|
|
{ id: "qwen-image", name: "Qwen Image", inputModalities: ["text", "image"] },
|
|
{ id: "z-image-turbo", name: "Z Image Turbo" },
|
|
{ id: "chroma", name: "Chroma" },
|
|
{ id: "hidream", name: "Hidream I1 Full" },
|
|
],
|
|
supportedSizes: ["1024x1024", "1024x1280", "1280x1024"],
|
|
},
|
|
};
|
|
}
|
|
return _IMAGE_PROVIDERS;
|
|
}
|
|
|
|
export function getImageProviders(): Record<string, ImageProviderConfig> {
|
|
return IMAGE_PROVIDERS;
|
|
}
|
|
|
|
export const IMAGE_PROVIDERS = new Proxy({} as Record<string, ImageProviderConfig>, {
|
|
get(target, key: string) {
|
|
if (key in target) {
|
|
return target[key];
|
|
}
|
|
return getOrCreateImageProviders()[key];
|
|
},
|
|
set(target, key: string, value) {
|
|
target[key] = value;
|
|
getOrCreateImageProviders()[key] = value;
|
|
return true;
|
|
},
|
|
deleteProperty(target, key: string) {
|
|
delete target[key];
|
|
delete getOrCreateImageProviders()[key];
|
|
return true;
|
|
},
|
|
ownKeys(target) {
|
|
const targetKeys = Reflect.ownKeys(target);
|
|
const registryKeys = Reflect.ownKeys(getOrCreateImageProviders());
|
|
return Array.from(new Set([...targetKeys, ...registryKeys]));
|
|
},
|
|
has(target, key) {
|
|
return key in target || key in getOrCreateImageProviders();
|
|
},
|
|
getOwnPropertyDescriptor(target, key) {
|
|
if (key in target) {
|
|
return Reflect.getOwnPropertyDescriptor(target, key);
|
|
}
|
|
if (key in getOrCreateImageProviders()) {
|
|
return { configurable: true, enumerable: true, value: getOrCreateImageProviders()[key as string] };
|
|
}
|
|
return undefined;
|
|
},
|
|
});
|
|
|
|
export function getImageProvider(providerId) {
|
|
return IMAGE_PROVIDERS[providerId] || null;
|
|
}
|
|
/**
|
|
* Parse image model string (format: "provider/model")
|
|
* Returns { provider, model }
|
|
*/
|
|
export function parseImageModel(modelStr) {
|
|
if (!modelStr) return { provider: null, model: null };
|
|
|
|
const directAlias = resolveImageModelAlias(modelStr);
|
|
if (directAlias) {
|
|
return directAlias;
|
|
}
|
|
|
|
// Try each provider prefix
|
|
for (const [providerId, config] of Object.entries(IMAGE_PROVIDERS)) {
|
|
if (modelStr.startsWith(providerId + "/")) {
|
|
const model = modelStr.slice(providerId.length + 1);
|
|
const aliased =
|
|
resolveImageModelAlias(`${providerId}/${model}`) || resolveImageModelAlias(model);
|
|
return aliased || { provider: providerId, model };
|
|
}
|
|
// Check alias if available
|
|
if (config.alias && modelStr.startsWith(config.alias + "/")) {
|
|
const model = modelStr.slice(config.alias.length + 1);
|
|
const aliased =
|
|
resolveImageModelAlias(`${providerId}/${model}`) || resolveImageModelAlias(model);
|
|
return aliased || { provider: providerId, model };
|
|
}
|
|
}
|
|
|
|
// No provider prefix — try to find the model in every provider
|
|
for (const [providerId, config] of Object.entries(IMAGE_PROVIDERS)) {
|
|
if (config.models.some((m) => m.id === modelStr)) {
|
|
return { provider: providerId, model: modelStr };
|
|
}
|
|
}
|
|
|
|
return { provider: null, model: modelStr };
|
|
}
|
|
|
|
/**
|
|
* Get all image models as a flat list
|
|
*/
|
|
export function getAllImageModels() {
|
|
const models = [];
|
|
for (const [providerId, config] of Object.entries(IMAGE_PROVIDERS)) {
|
|
for (const model of config.models) {
|
|
models.push({
|
|
id: `${providerId}/${model.id}`,
|
|
name: model.name,
|
|
provider: providerId,
|
|
supportedSizes: config.supportedSizes,
|
|
inputModalities: model.inputModalities || ["text"],
|
|
description: model.description || undefined,
|
|
});
|
|
}
|
|
}
|
|
for (const [alias, target] of Object.entries(IMAGE_MODEL_ALIASES)) {
|
|
if (!target.listInCatalog) continue;
|
|
const providerConfig = IMAGE_PROVIDERS[target.provider];
|
|
const modelConfig = findImageModelConfig(target.provider, target.model);
|
|
models.push({
|
|
id: alias,
|
|
name: target.name || modelConfig?.name || alias,
|
|
provider: target.provider,
|
|
supportedSizes: providerConfig?.supportedSizes || [],
|
|
inputModalities: target.inputModalities || modelConfig?.inputModalities || ["text"],
|
|
description: target.description || modelConfig?.description || undefined,
|
|
});
|
|
}
|
|
return models;
|
|
}
|
|
|
|
export function getImageModelAliases() {
|
|
return IMAGE_MODEL_ALIASES;
|
|
}
|
|
|
|
export function getImageModelEntry(modelStr) {
|
|
if (!modelStr) return null;
|
|
|
|
const alias = IMAGE_MODEL_ALIASES[modelStr];
|
|
if (alias) {
|
|
const modelConfig = findImageModelConfig(alias.provider, alias.model);
|
|
return {
|
|
provider: alias.provider,
|
|
model: alias.model,
|
|
inputModalities: alias.inputModalities || modelConfig?.inputModalities || ["text"],
|
|
description: alias.description || modelConfig?.description || undefined,
|
|
};
|
|
}
|
|
|
|
const { provider, model } = parseImageModel(modelStr);
|
|
if (!provider || !model) return null;
|
|
|
|
const modelConfig = findImageModelConfig(provider, model);
|
|
if (!modelConfig) return null;
|
|
|
|
return {
|
|
provider,
|
|
model,
|
|
inputModalities: modelConfig.inputModalities || ["text"],
|
|
description: modelConfig.description || undefined,
|
|
};
|
|
}
|