From 9806f0ae0ac82110a51937f1d307f44883f78165 Mon Sep 17 00:00:00 2001 From: shrimbly Date: Wed, 4 Feb 2026 22:31:13 +1300 Subject: [PATCH] feat: add all missing Kie.ai models + fix model page links Add ~38 new image/video models (Imagen 4, FLUX.2, Flux Kontext, Grok Imagine, Ideogram, Qwen, Runway, Hailuo, Seedance variants, Kling older, Wan older) with schemas and endpoint routing for dedicated APIs (Flux Kontext, Runway). Add pageUrl to all Kie models and update ModelSearchDialog to use it. Co-Authored-By: Claude Opus 4.5 --- src/app/api/generate/route.ts | 169 ++++++++- src/app/api/models/[modelId]/route.ts | 277 +++++++++++++++ src/app/api/models/route.ts | 365 +++++++++++++++++++- src/components/modals/ModelSearchDialog.tsx | 98 ++++-- src/lib/providers/types.ts | 2 + 5 files changed, 870 insertions(+), 41 deletions(-) diff --git a/src/app/api/generate/route.ts b/src/app/api/generate/route.ts index 879255cd..56eeb440 100644 --- a/src/app/api/generate/route.ts +++ b/src/app/api/generate/route.ts @@ -1328,6 +1328,110 @@ function getKieModelDefaults(modelId: string): Record { // Wan video models case "wan/2-6-text-to-video": case "wan/2-6-image-to-video": + case "wan/2-2-a14b-text-to-video-turbo": + case "wan/2-2-a14b-image-to-video-turbo": + case "wan/2-6-video-to-video": + return { + aspect_ratio: "16:9", + duration: "5", + }; + + // Imagen 4 models + case "google/imagen4": + case "google/imagen4-fast": + case "google/imagen4-ultra": + return { + aspect_ratio: "1:1", + }; + + // Google Pro image-to-image + case "google/pro-image-to-image": + return { + aspect_ratio: "1:1", + }; + + // Flux-2 models + case "flux-2/pro-text-to-image": + case "flux-2/pro-image-to-image": + case "flux-2/flex-text-to-image": + case "flux-2/flex-image-to-image": + return { + aspect_ratio: "1:1", + }; + + // Flux Kontext models (camelCase params, top-level) + case "flux-kontext-pro": + case "flux-kontext-max": + return { + aspectRatio: "1:1", + outputFormat: "png", + }; + + // Grok Imagine models + case "grok-imagine/text-to-image": + case "grok-imagine/image-to-image": + return { + aspect_ratio: "1:1", + }; + + // Grok Imagine video models + case "grok-imagine/text-to-video": + case "grok-imagine/image-to-video": + return { + aspect_ratio: "16:9", + duration: "5", + }; + + // Ideogram models + case "ideogram/v3-reframe": + case "ideogram/character": + case "ideogram/character-edit": + return { + aspect_ratio: "1:1", + }; + + // Qwen models + case "qwen/text-to-image": + case "qwen/image-to-image": + case "qwen/image-edit": + return { + aspect_ratio: "1:1", + }; + + // Runway models (camelCase params, top-level) + case "runway/gen4": + case "runway/aleph": + return { + aspectRatio: "16:9", + duration: 10, + }; + + // Hailuo video models + case "hailuo/2-3-image-to-video-pro": + case "hailuo/2-3-image-to-video-standard": + case "hailuo/02-text-to-video-pro": + case "hailuo/02-image-to-video-pro": + return { + aspect_ratio: "16:9", + duration: "5", + }; + + // Bytedance Seedance variants + case "bytedance/v1-pro-text-to-video": + case "bytedance/v1-pro-image-to-video": + case "bytedance/v1-pro-fast-image-to-video": + case "bytedance/v1-lite-text-to-video": + case "bytedance/v1-lite-image-to-video": + return { + aspect_ratio: "16:9", + duration: "5", + }; + + // Kling older models + case "kling/v2-1-master-text-to-video": + case "kling/v2-1-master-image-to-video": + case "kling/v2-5-turbo-text-to-video-pro": + case "kling/v2-5-turbo-image-to-video-pro": return { aspect_ratio: "16:9", duration: "5", @@ -1347,6 +1451,10 @@ function getKieImageInputKey(modelId: string): string { if (modelId === "seedream/4.5-edit") return "image_urls"; if (modelId === "gpt-image/1.5-image-to-image") return "input_urls"; if (modelId === "veo3" || modelId === "veo3_fast") return "imageUrls"; + // Flux Kontext uses singular image_url + if (modelId.startsWith("flux-kontext-")) return "image_url"; + // Runway uses camelCase imageUrl + if (modelId.startsWith("runway/")) return "imageUrl"; // Default for most models return "image_urls"; } @@ -1358,6 +1466,20 @@ function isKieVeoModel(modelId: string): boolean { return modelId === "veo3" || modelId === "veo3_fast"; } +/** + * Check if a model is a Flux Kontext model (uses dedicated endpoint) + */ +function isKieFluxKontextModel(modelId: string): boolean { + return modelId.startsWith("flux-kontext-"); +} + +/** + * Check if a model is a Runway model (uses dedicated endpoint) + */ +function isKieRunwayModel(modelId: string): boolean { + return modelId.startsWith("runway/"); +} + /** * Detect actual image type from binary data (magic bytes) */ @@ -1524,8 +1646,10 @@ async function generateWithKie( ): Promise { const modelId = input.model.id; const isVeo = isKieVeoModel(modelId); + const isFluxKontext = isKieFluxKontextModel(modelId); + const isRunway = isKieRunwayModel(modelId); - console.log(`[API:${requestId}] Kie.ai generation - Model: ${modelId}, Images: ${input.images?.length || 0}, Prompt: ${input.prompt.length} chars, Veo: ${isVeo}`); + console.log(`[API:${requestId}] Kie.ai generation - Model: ${modelId}, Images: ${input.images?.length || 0}, Prompt: ${input.prompt.length} chars, Veo: ${isVeo}, FluxKontext: ${isFluxKontext}, Runway: ${isRunway}`); // Build the input object (all parameters go inside "input" for Kie API) // Start with model-specific required defaults @@ -1600,7 +1724,7 @@ async function generateWithKie( } } - // Build request body - Veo uses different format (no input wrapper, camelCase) + // Build request body - dedicated endpoints use top-level params (no input wrapper) let requestBody: Record; if (isVeo) { @@ -1619,6 +1743,34 @@ async function generateWithKie( } else if (inputParams.image_urls) { requestBody.imageUrls = inputParams.image_urls; } + } else if (isFluxKontext) { + // Flux Kontext format: top-level camelCase params, model name derived from ID + requestBody = { + model: modelId === "flux-kontext-max" ? "max" : "pro", + prompt: inputParams.prompt, + aspectRatio: inputParams.aspectRatio || inputParams.aspect_ratio || "1:1", + }; + if (inputParams.outputFormat) requestBody.outputFormat = inputParams.outputFormat; + if (inputParams.safetyTolerance !== undefined) requestBody.safetyTolerance = inputParams.safetyTolerance; + if (inputParams.seed !== undefined) requestBody.seed = inputParams.seed; + // Singular image_url for Kontext + if (inputParams.image_url) { + requestBody.image_url = Array.isArray(inputParams.image_url) ? inputParams.image_url[0] : inputParams.image_url; + } + } else if (isRunway) { + // Runway format: top-level camelCase params + requestBody = { + model: modelId, + prompt: inputParams.prompt, + aspectRatio: inputParams.aspectRatio || inputParams.aspect_ratio || "16:9", + }; + if (inputParams.duration !== undefined) requestBody.duration = inputParams.duration; + if (inputParams.quality) requestBody.quality = inputParams.quality; + if (inputParams.seed !== undefined) requestBody.seed = inputParams.seed; + // Singular imageUrl for Runway + if (inputParams.imageUrl) { + requestBody.imageUrl = Array.isArray(inputParams.imageUrl) ? inputParams.imageUrl[0] : inputParams.imageUrl; + } } else { // Standard format: { model, input: { ... } } requestBody = { @@ -1628,9 +1780,16 @@ async function generateWithKie( } // Select endpoint based on model type - const createUrl = isVeo - ? "https://api.kie.ai/api/v1/veo/generate" - : "https://api.kie.ai/api/v1/jobs/createTask"; + let createUrl: string; + if (isVeo) { + createUrl = "https://api.kie.ai/api/v1/veo/generate"; + } else if (isFluxKontext) { + createUrl = "https://api.kie.ai/api/v1/flux/kontext/generate"; + } else if (isRunway) { + createUrl = "https://api.kie.ai/api/v1/runway/generate"; + } else { + createUrl = "https://api.kie.ai/api/v1/jobs/createTask"; + } console.log(`[API:${requestId}] Calling Kie.ai API: ${createUrl}`); // Log full request body for debugging (truncate very long prompts) diff --git a/src/app/api/models/[modelId]/route.ts b/src/app/api/models/[modelId]/route.ts index eabe674c..5f005f57 100644 --- a/src/app/api/models/[modelId]/route.ts +++ b/src/app/api/models/[modelId]/route.ts @@ -657,6 +657,283 @@ function getKieSchema(modelId: string): ExtractedSchema { { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, ], }, + // --- New image models --- + "google/imagen4": { + parameters: imageParams, + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "google/imagen4-fast": { + parameters: imageParams, + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "google/imagen4-ultra": { + parameters: imageParams, + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "google/pro-image-to-image": { + parameters: imageParams, + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "flux-2/pro-text-to-image": { + parameters: [ + ...imageParams, + { name: "resolution", type: "string", description: "Output resolution", enum: ["1K", "2K"], default: "1K" }, + ], + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "flux-2/pro-image-to-image": { + parameters: [ + ...imageParams, + { name: "resolution", type: "string", description: "Output resolution", enum: ["1K", "2K"], default: "1K" }, + ], + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "flux-2/flex-text-to-image": { + parameters: [ + ...imageParams, + { name: "resolution", type: "string", description: "Output resolution", enum: ["1K", "2K"], default: "1K" }, + ], + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "flux-2/flex-image-to-image": { + parameters: [ + ...imageParams, + { name: "resolution", type: "string", description: "Output resolution", enum: ["1K", "2K"], default: "1K" }, + ], + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "flux-kontext-pro": { + parameters: [ + { name: "aspectRatio", type: "string", description: "Output aspect ratio", enum: ["1:1", "16:9", "9:16", "4:3", "3:4", "21:9", "9:21"], default: "1:1" }, + { name: "outputFormat", type: "string", description: "Output image format", enum: ["png", "jpeg", "webp"], default: "png" }, + { name: "safetyTolerance", type: "integer", description: "Safety tolerance level (0=strict, 6=permissive)", minimum: 0, maximum: 6, default: 2 }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "image_url", type: "image", required: false, label: "Image" }, + ], + }, + "flux-kontext-max": { + parameters: [ + { name: "aspectRatio", type: "string", description: "Output aspect ratio", enum: ["1:1", "16:9", "9:16", "4:3", "3:4", "21:9", "9:21"], default: "1:1" }, + { name: "outputFormat", type: "string", description: "Output image format", enum: ["png", "jpeg", "webp"], default: "png" }, + { name: "safetyTolerance", type: "integer", description: "Safety tolerance level (0=strict, 6=permissive)", minimum: 0, maximum: 6, default: 2 }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "image_url", type: "image", required: false, label: "Image" }, + ], + }, + "grok-imagine/text-to-image": { + parameters: imageParams, + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "grok-imagine/image-to-image": { + parameters: imageParams, + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "ideogram/v3-reframe": { + parameters: imageParams, + inputs: [ + { name: "prompt", type: "text", required: false, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "ideogram/character": { + parameters: imageParams, + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "ideogram/character-edit": { + parameters: imageParams, + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "qwen/text-to-image": { + parameters: imageParams, + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "qwen/image-to-image": { + parameters: imageParams, + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "qwen/image-edit": { + parameters: imageParams, + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + // --- New video models --- + "runway/gen4": { + parameters: [ + { name: "duration", type: "integer", description: "Video duration in seconds", minimum: 5, maximum: 10, default: 10 }, + { name: "quality", type: "string", description: "Output quality", enum: ["standard", "high"], default: "standard" }, + { name: "aspectRatio", type: "string", description: "Output aspect ratio", enum: ["16:9", "9:16", "1:1"], default: "16:9" }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "imageUrl", type: "image", required: false, label: "Image" }, + ], + }, + "runway/aleph": { + parameters: [ + { name: "duration", type: "integer", description: "Video duration in seconds", minimum: 5, maximum: 10, default: 10 }, + { name: "quality", type: "string", description: "Output quality", enum: ["standard", "high"], default: "standard" }, + { name: "aspectRatio", type: "string", description: "Output aspect ratio", enum: ["16:9", "9:16", "1:1"], default: "16:9" }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "imageUrl", type: "image", required: false, label: "Image" }, + ], + }, + "grok-imagine/text-to-video": { + parameters: videoParams, + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "grok-imagine/image-to-video": { + parameters: videoParams, + inputs: [ + { name: "prompt", type: "text", required: false, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "hailuo/2-3-image-to-video-pro": { + parameters: videoParams, + inputs: [ + { name: "prompt", type: "text", required: false, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "hailuo/2-3-image-to-video-standard": { + parameters: videoParams, + inputs: [ + { name: "prompt", type: "text", required: false, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "hailuo/02-text-to-video-pro": { + parameters: videoParams, + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "hailuo/02-image-to-video-pro": { + parameters: videoParams, + inputs: [ + { name: "prompt", type: "text", required: false, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "bytedance/v1-pro-text-to-video": { + parameters: videoParams, + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "image_urls", type: "image", required: false, label: "Image", isArray: true }, + ], + }, + "bytedance/v1-pro-image-to-video": { + parameters: videoParams, + inputs: [ + { name: "prompt", type: "text", required: false, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "bytedance/v1-pro-fast-image-to-video": { + parameters: videoParams, + inputs: [ + { name: "prompt", type: "text", required: false, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "bytedance/v1-lite-text-to-video": { + parameters: videoParams, + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "image_urls", type: "image", required: false, label: "Image", isArray: true }, + ], + }, + "bytedance/v1-lite-image-to-video": { + parameters: videoParams, + inputs: [ + { name: "prompt", type: "text", required: false, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "kling/v2-1-master-text-to-video": { + parameters: [ + { name: "aspect_ratio", type: "string", description: "Output aspect ratio", enum: ["16:9", "9:16", "1:1"], default: "16:9" }, + { name: "duration", type: "string", description: "Video duration", enum: ["5", "10"], default: "5" }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "kling/v2-1-master-image-to-video": { + parameters: [ + { name: "aspect_ratio", type: "string", description: "Output aspect ratio", enum: ["16:9", "9:16", "1:1"], default: "16:9" }, + { name: "duration", type: "string", description: "Video duration", enum: ["5", "10"], default: "5" }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [ + { name: "prompt", type: "text", required: false, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "kling/v2-5-turbo-text-to-video-pro": { + parameters: [ + { name: "aspect_ratio", type: "string", description: "Output aspect ratio", enum: ["16:9", "9:16", "1:1"], default: "16:9" }, + { name: "duration", type: "string", description: "Video duration", enum: ["5", "10"], default: "5" }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "kling/v2-5-turbo-image-to-video-pro": { + parameters: [ + { name: "aspect_ratio", type: "string", description: "Output aspect ratio", enum: ["16:9", "9:16", "1:1"], default: "16:9" }, + { name: "duration", type: "string", description: "Video duration", enum: ["5", "10"], default: "5" }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [ + { name: "prompt", type: "text", required: false, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "wan/2-2-a14b-text-to-video-turbo": { + parameters: videoParams, + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "wan/2-2-a14b-image-to-video-turbo": { + parameters: videoParams, + inputs: [ + { name: "prompt", type: "text", required: false, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "wan/2-6-video-to-video": { + parameters: videoParams, + inputs: [ + { name: "prompt", type: "text", required: false, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Video/Image", isArray: true }, + ], + }, }; return schemas[modelId] || { parameters: [], inputs: [] }; diff --git a/src/app/api/models/route.ts b/src/app/api/models/route.ts index 7fe9cab2..468c87df 100644 --- a/src/app/api/models/route.ts +++ b/src/app/api/models/route.ts @@ -53,7 +53,7 @@ const RELEVANT_CATEGORIES = [ // Kie.ai models (hardcoded - no discovery API available) const KIE_MODELS: ProviderModel[] = [ - // Image Models + // ============ Image Models ============ { id: "z-image", name: "Z-Image", @@ -62,6 +62,7 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["text-to-image"], coverImage: undefined, pricing: { type: "per-run", amount: 0.004, currency: "USD" }, + pageUrl: "https://kie.ai/z-image", }, { id: "seedream/4.5-text-to-image", @@ -71,6 +72,7 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["text-to-image"], coverImage: undefined, pricing: { type: "per-run", amount: 0.032, currency: "USD" }, + pageUrl: "https://kie.ai/seedream", }, { id: "seedream/4.5-edit", @@ -80,6 +82,7 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["image-to-image"], coverImage: undefined, pricing: { type: "per-run", amount: 0.032, currency: "USD" }, + pageUrl: "https://kie.ai/seedream", }, { id: "nano-banana-pro", @@ -89,6 +92,7 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["text-to-image", "image-to-image"], coverImage: undefined, pricing: { type: "per-run", amount: 0.10, currency: "USD" }, + pageUrl: "https://kie.ai/nano-banana", }, { id: "gpt-image/1.5-text-to-image", @@ -98,6 +102,7 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["text-to-image"], coverImage: undefined, pricing: { type: "per-run", amount: 0.06, currency: "USD" }, + pageUrl: "https://kie.ai/gpt-image-1", }, { id: "gpt-image/1.5-image-to-image", @@ -107,6 +112,7 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["image-to-image"], coverImage: undefined, pricing: { type: "per-run", amount: 0.06, currency: "USD" }, + pageUrl: "https://kie.ai/gpt-image-1", }, { id: "google/nano-banana", @@ -116,6 +122,7 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["text-to-image"], coverImage: undefined, pricing: { type: "per-run", amount: 0.05, currency: "USD" }, + pageUrl: "https://kie.ai/nano-banana", }, { id: "google/nano-banana-edit", @@ -125,8 +132,171 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["image-to-image"], coverImage: undefined, pricing: { type: "per-run", amount: 0.05, currency: "USD" }, + pageUrl: "https://kie.ai/nano-banana", }, - // Video Models + { + id: "google/imagen4", + name: "Imagen 4", + description: "Google Imagen 4 text-to-image generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/google/imagen4", + }, + { + id: "google/imagen4-fast", + name: "Imagen 4 Fast", + description: "Google Imagen 4 fast text-to-image generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/google/imagen4", + }, + { + id: "google/imagen4-ultra", + name: "Imagen 4 Ultra", + description: "Google Imagen 4 Ultra high-quality text-to-image generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/google/imagen4", + }, + { + id: "google/pro-image-to-image", + name: "Gemini Pro Edit", + description: "Google Gemini Pro image editing via Kie.ai.", + provider: "kie", + capabilities: ["image-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/nano-banana", + }, + { + id: "flux-2/pro-text-to-image", + name: "FLUX.2 Pro", + description: "FLUX.2 Pro text-to-image generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/flux-2", + }, + { + id: "flux-2/pro-image-to-image", + name: "FLUX.2 Pro Edit", + description: "FLUX.2 Pro image editing via Kie.ai.", + provider: "kie", + capabilities: ["image-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/flux-2", + }, + { + id: "flux-2/flex-text-to-image", + name: "FLUX.2 Flex", + description: "FLUX.2 Flex text-to-image generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/flux-2", + }, + { + id: "flux-2/flex-image-to-image", + name: "FLUX.2 Flex Edit", + description: "FLUX.2 Flex image editing via Kie.ai.", + provider: "kie", + capabilities: ["image-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/flux-2", + }, + { + id: "flux-kontext-pro", + name: "Flux Kontext Pro", + description: "Flux Kontext Pro image generation and editing via Kie.ai.", + provider: "kie", + capabilities: ["text-to-image", "image-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/features/flux1-kontext", + }, + { + id: "flux-kontext-max", + name: "Flux Kontext Max", + description: "Flux Kontext Max image generation and editing via Kie.ai.", + provider: "kie", + capabilities: ["text-to-image", "image-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/features/flux1-kontext", + }, + { + id: "grok-imagine/text-to-image", + name: "Grok Imagine", + description: "Grok Imagine text-to-image generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/grok-imagine", + }, + { + id: "grok-imagine/image-to-image", + name: "Grok Imagine Edit", + description: "Grok Imagine image editing via Kie.ai.", + provider: "kie", + capabilities: ["image-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/grok-imagine", + }, + { + id: "ideogram/v3-reframe", + name: "Ideogram V3 Reframe", + description: "Ideogram V3 image reframing via Kie.ai.", + provider: "kie", + capabilities: ["image-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/ideogram-v3", + }, + { + id: "ideogram/character", + name: "Ideogram Character", + description: "Ideogram character-consistent text-to-image generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/ideogram-v3", + }, + { + id: "ideogram/character-edit", + name: "Ideogram Character Edit", + description: "Ideogram character-consistent image editing via Kie.ai.", + provider: "kie", + capabilities: ["image-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/ideogram-v3", + }, + { + id: "qwen/text-to-image", + name: "Qwen Image", + description: "Qwen text-to-image generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/qwen-image", + }, + { + id: "qwen/image-to-image", + name: "Qwen Image Edit", + description: "Qwen image editing via Kie.ai.", + provider: "kie", + capabilities: ["image-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/qwen-image", + }, + { + id: "qwen/image-edit", + name: "Qwen Image Inpaint", + description: "Qwen image inpainting via Kie.ai.", + provider: "kie", + capabilities: ["image-to-image"], + coverImage: undefined, + pageUrl: "https://kie.ai/qwen-image", + }, + // ============ Video Models ============ { id: "sora-2-text-to-video", name: "Sora 2", @@ -135,6 +305,7 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["text-to-video"], coverImage: undefined, pricing: { type: "per-second", amount: 0.015, currency: "USD" }, + pageUrl: "https://kie.ai/sora", }, { id: "sora-2-image-to-video", @@ -144,6 +315,7 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["image-to-video"], coverImage: undefined, pricing: { type: "per-second", amount: 0.015, currency: "USD" }, + pageUrl: "https://kie.ai/sora", }, { id: "sora-2-pro-text-to-video", @@ -153,6 +325,7 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["text-to-video"], coverImage: undefined, pricing: { type: "per-second", amount: 0.10, currency: "USD" }, + pageUrl: "https://kie.ai/sora", }, { id: "sora-2-pro-image-to-video", @@ -162,6 +335,7 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["image-to-video"], coverImage: undefined, pricing: { type: "per-second", amount: 0.10, currency: "USD" }, + pageUrl: "https://kie.ai/sora", }, { id: "veo3_fast", @@ -171,6 +345,7 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["text-to-video", "image-to-video"], coverImage: undefined, pricing: { type: "per-run", amount: 0.30, currency: "USD" }, + pageUrl: "https://kie.ai/veo3", }, { id: "veo3", @@ -180,6 +355,7 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["text-to-video", "image-to-video"], coverImage: undefined, pricing: { type: "per-run", amount: 1.25, currency: "USD" }, + pageUrl: "https://kie.ai/veo3", }, { id: "bytedance/seedance-1.5-pro", @@ -189,6 +365,7 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["text-to-video", "image-to-video"], coverImage: undefined, pricing: { type: "per-run", amount: 0.20, currency: "USD" }, + pageUrl: "https://kie.ai/seedance-1-5-pro", }, { id: "kling-2.6/text-to-video", @@ -198,6 +375,7 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["text-to-video"], coverImage: undefined, pricing: { type: "per-run", amount: 0.60, currency: "USD" }, + pageUrl: "https://kie.ai/kling-2-6", }, { id: "kling-2.6/image-to-video", @@ -207,6 +385,7 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["image-to-video"], coverImage: undefined, pricing: { type: "per-run", amount: 0.60, currency: "USD" }, + pageUrl: "https://kie.ai/kling-2-6", }, { id: "wan/2-6-text-to-video", @@ -216,6 +395,7 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["text-to-video"], coverImage: undefined, pricing: { type: "per-run", amount: 0.90, currency: "USD" }, + pageUrl: "https://kie.ai/wan-2-6", }, { id: "wan/2-6-image-to-video", @@ -225,6 +405,187 @@ const KIE_MODELS: ProviderModel[] = [ capabilities: ["image-to-video"], coverImage: undefined, pricing: { type: "per-run", amount: 0.90, currency: "USD" }, + pageUrl: "https://kie.ai/wan-2-6", + }, + { + id: "runway/gen4", + name: "Runway Gen-4", + description: "Runway Gen-4 video generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-video", "image-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/runway/gen4-turbo", + }, + { + id: "runway/aleph", + name: "Runway Aleph", + description: "Runway Aleph video generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-video", "image-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/runway/gen4-aleph", + }, + { + id: "grok-imagine/text-to-video", + name: "Grok Imagine Video", + description: "Grok Imagine text-to-video generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/grok-imagine", + }, + { + id: "grok-imagine/image-to-video", + name: "Grok Imagine I2V", + description: "Grok Imagine image-to-video generation via Kie.ai.", + provider: "kie", + capabilities: ["image-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/grok-imagine", + }, + { + id: "hailuo/2-3-image-to-video-pro", + name: "Hailuo 2.3 Pro", + description: "Hailuo 2.3 Pro image-to-video generation via Kie.ai.", + provider: "kie", + capabilities: ["image-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/hailuo-2-3", + }, + { + id: "hailuo/2-3-image-to-video-standard", + name: "Hailuo 2.3", + description: "Hailuo 2.3 standard image-to-video generation via Kie.ai.", + provider: "kie", + capabilities: ["image-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/hailuo-2-3", + }, + { + id: "hailuo/02-text-to-video-pro", + name: "Hailuo Pro", + description: "Hailuo Pro text-to-video generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/hailuo-2-3", + }, + { + id: "hailuo/02-image-to-video-pro", + name: "Hailuo Pro I2V", + description: "Hailuo Pro image-to-video generation via Kie.ai.", + provider: "kie", + capabilities: ["image-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/hailuo-2-3", + }, + { + id: "bytedance/v1-pro-text-to-video", + name: "Seedance Pro", + description: "ByteDance Seedance Pro text-to-video generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/seedance-1-5-pro", + }, + { + id: "bytedance/v1-pro-image-to-video", + name: "Seedance Pro I2V", + description: "ByteDance Seedance Pro image-to-video generation via Kie.ai.", + provider: "kie", + capabilities: ["image-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/seedance-1-5-pro", + }, + { + id: "bytedance/v1-pro-fast-image-to-video", + name: "Seedance Pro Fast", + description: "ByteDance Seedance Pro fast image-to-video generation via Kie.ai.", + provider: "kie", + capabilities: ["image-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/seedance-1-5-pro", + }, + { + id: "bytedance/v1-lite-text-to-video", + name: "Seedance Lite", + description: "ByteDance Seedance Lite text-to-video generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/seedance-1-5-pro", + }, + { + id: "bytedance/v1-lite-image-to-video", + name: "Seedance Lite I2V", + description: "ByteDance Seedance Lite image-to-video generation via Kie.ai.", + provider: "kie", + capabilities: ["image-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/seedance-1-5-pro", + }, + { + id: "kling/v2-1-master-text-to-video", + name: "Kling 2.1 Master", + description: "Kling 2.1 Master text-to-video generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/kling-2-6", + }, + { + id: "kling/v2-1-master-image-to-video", + name: "Kling 2.1 Master I2V", + description: "Kling 2.1 Master image-to-video generation via Kie.ai.", + provider: "kie", + capabilities: ["image-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/kling-2-6", + }, + { + id: "kling/v2-5-turbo-text-to-video-pro", + name: "Kling 2.5 Turbo", + description: "Kling 2.5 Turbo text-to-video generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/kling-2-6", + }, + { + id: "kling/v2-5-turbo-image-to-video-pro", + name: "Kling 2.5 Turbo I2V", + description: "Kling 2.5 Turbo image-to-video generation via Kie.ai.", + provider: "kie", + capabilities: ["image-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/kling-2-6", + }, + { + id: "wan/2-2-a14b-text-to-video-turbo", + name: "Wan 2.2 Turbo", + description: "Wan 2.2 Turbo text-to-video generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/wan-2-6", + }, + { + id: "wan/2-2-a14b-image-to-video-turbo", + name: "Wan 2.2 Turbo I2V", + description: "Wan 2.2 Turbo image-to-video generation via Kie.ai.", + provider: "kie", + capabilities: ["image-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/wan-2-6", + }, + { + id: "wan/2-6-video-to-video", + name: "Wan 2.6 V2V", + description: "Wan 2.6 video-to-video transformation via Kie.ai.", + provider: "kie", + capabilities: ["image-to-video"], + coverImage: undefined, + pageUrl: "https://kie.ai/wan-2-6", }, ]; diff --git a/src/components/modals/ModelSearchDialog.tsx b/src/components/modals/ModelSearchDialog.tsx index 52c1e8d5..262eb273 100644 --- a/src/components/modals/ModelSearchDialog.tsx +++ b/src/components/modals/ModelSearchDialog.tsx @@ -8,9 +8,9 @@ import { useReactFlow } from "@xyflow/react"; import { ProviderType, RecentModel } from "@/types"; import { ProviderModel, ModelCapability } from "@/lib/providers/types"; -// Provider icons +// Provider icons — all normalized to w-3.5 h-3.5 with viewBoxes cropped to fill consistently const ReplicateIcon = () => ( - + @@ -18,19 +18,25 @@ const ReplicateIcon = () => ( ); const FalIcon = () => ( - + ); const GeminiIcon = () => ( - + ); +const KieIcon = () => ( + + + +); + const WaveSpeedIcon = () => ( - + @@ -85,7 +91,7 @@ export function ModelSearchDialog({ trackModelUsage, } = useWorkflowStore(); // Use stable selector for API keys to prevent unnecessary re-fetches - const { replicateApiKey, falApiKey } = useProviderApiKeys(); + const { replicateApiKey, falApiKey, kieApiKey, wavespeedApiKey } = useProviderApiKeys(); const { screenToFlowPosition } = useReactFlow(); // State @@ -161,6 +167,12 @@ export function ModelSearchDialog({ if (falApiKey) { headers["X-Fal-Key"] = falApiKey; } + if (kieApiKey) { + headers["X-Kie-Key"] = kieApiKey; + } + if (wavespeedApiKey) { + headers["X-WaveSpeed-Key"] = wavespeedApiKey; + } const response = await deduplicatedFetch(`/api/models?${params.toString()}`, { headers, @@ -192,7 +204,7 @@ export function ModelSearchDialog({ setIsLoading(false); } } - }, [debouncedSearch, providerFilter, capabilityFilter, replicateApiKey, falApiKey]); + }, [debouncedSearch, providerFilter, capabilityFilter, replicateApiKey, falApiKey, kieApiKey, wavespeedApiKey]); // Fetch models when filters change useEffect(() => { @@ -358,13 +370,18 @@ export function ModelSearchDialog({ }; // Get model page URL for the provider's website - const getModelUrl = (model: ProviderModel): string => { - if (model.provider === "replicate") { - return `https://replicate.com/${model.id}`; - } else if (model.provider === "fal") { - return `https://fal.ai/models/${model.id}`; + const getModelUrl = (model: ProviderModel): string | null => { + if (model.pageUrl) return model.pageUrl; + switch (model.provider) { + case "replicate": + return `https://replicate.com/${model.id}`; + case "fal": + return `https://fal.ai/models/${model.id}`; + case "wavespeed": + return `https://wavespeed.ai`; + default: + return null; } - return "#"; }; // Get capability badges - show all capabilities to differentiate similar models @@ -516,6 +533,17 @@ export function ModelSearchDialog({ > + {model.id} - e.stopPropagation()} - className="text-neutral-500 hover:text-neutral-300 transition-colors flex-shrink-0" - title={`View on ${getProviderDisplayName(model.provider)}`} - > - e.stopPropagation()} + className="text-neutral-500 hover:text-neutral-300 transition-colors flex-shrink-0" + title={`View on ${getProviderDisplayName(model.provider)}`} > - - - + + + + + )} {/* Badges row */} diff --git a/src/lib/providers/types.ts b/src/lib/providers/types.ts index 2280da3d..ea5faff6 100644 --- a/src/lib/providers/types.ts +++ b/src/lib/providers/types.ts @@ -72,6 +72,8 @@ export interface ProviderModel { amount: number; currency: string; }; + /** Optional URL to the model's page on the provider's website */ + pageUrl?: string; } /**