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; } /**