diff --git a/src/app/api/generate/providers/kie.ts b/src/app/api/generate/providers/kie.ts index c33d005f..1d1a425f 100644 --- a/src/app/api/generate/providers/kie.ts +++ b/src/app/api/generate/providers/kie.ts @@ -39,6 +39,13 @@ export function getKieModelDefaults(modelId: string): Record { quality: "basic", }; + // Nano Banana 2 (Kie) + case "nano-banana-2": + return { + aspect_ratio: "auto", + resolution: "1K", + }; + // Nano Banana Pro (Kie) case "nano-banana-pro": return { @@ -55,6 +62,32 @@ export function getKieModelDefaults(modelId: string): Record { aspect_ratio: "1:1", }; + // Imagen 4 models + case "google/imagen4": + case "google/imagen4-ultra": + return {}; + + case "google/imagen4-fast": + return { + aspect_ratio: "16:9", + num_images: 1, + }; + + // Seedream 5.0 Lite models + case "seedream/5-lite-text-to-image": + case "seedream/5-lite-image-to-image": + return { + aspect_ratio: "1:1", + quality: "basic", + }; + + // Wan 2.7 Image + case "wan/2-7-image": + return { + resolution: "2K", + n: 4, + }; + // Grok Imagine image models case "grok-imagine/text-to-image": return { @@ -64,6 +97,19 @@ export function getKieModelDefaults(modelId: string): Record { case "grok-imagine/image-to-image": return {}; + // Seedance 2.0 models + case "bytedance/seedance-2/text-to-video": + case "bytedance/seedance-2/image-to-video": + case "bytedance/seedance-2-fast/text-to-video": + case "bytedance/seedance-2-fast/image-to-video": + return { + aspect_ratio: "16:9", + resolution: "720p", + duration: 8, + generate_audio: true, + web_search: false, + }; + // Grok Imagine video models case "grok-imagine/text-to-video": return { @@ -79,6 +125,23 @@ export function getKieModelDefaults(modelId: string): Record { mode: "normal", }; + // Kling 3.0 video models + case "kling-3.0/video/text-to-video": + case "kling-3.0/video/image-to-video": + return { + aspect_ratio: "16:9", + duration: "5", + mode: "pro", + }; + + // Kling 3.0 motion control + case "kling-3.0/motion-control": + return { + mode: "720p", + character_orientation: "video", + background_source: "input_video", + }; + // Kling 2.6 video models case "kling-2.6/text-to-video": case "kling-2.6/image-to-video": @@ -118,6 +181,20 @@ export function getKieModelDefaults(modelId: string): Record { resolution: "1080p", }; + // Wan 2.7 video models + case "wan/2-7-text-to-video": + return { + duration: 5, + resolution: "1080p", + ratio: "16:9", + }; + + case "wan/2-7-image-to-video": + return { + duration: 5, + resolution: "1080p", + }; + // Topaz video upscale case "topaz/video-upscale": return { @@ -158,15 +235,24 @@ export function getKieModelDefaults(modelId: string): Record { */ export function getKieImageInputKey(modelId: string): string { // Model-specific parameter names + if (modelId === "nano-banana-2") return "image_input"; if (modelId === "nano-banana-pro") return "image_input"; if (modelId === "seedream/4.5-edit") return "image_urls"; if (modelId === "gpt-image/1.5-image-to-image") return "input_urls"; // Flux-2 I2I models use input_urls if (modelId === "flux-2/pro-image-to-image" || modelId === "flux-2/flex-image-to-image") return "input_urls"; + // Wan 2.7 Image uses input_urls + if (modelId === "wan/2-7-image") return "input_urls"; + // Seedance I2V models use first_frame_url (singular) + if (modelId === "bytedance/seedance-2/image-to-video" || modelId === "bytedance/seedance-2-fast/image-to-video") return "first_frame_url"; // Kling 2.5 turbo I2V uses singular image_url if (modelId === "kling/v2-5-turbo-image-to-video-pro") return "image_url"; + // Kling 3.0 motion control uses input_urls + if (modelId === "kling-3.0/motion-control") return "input_urls"; // Kling 2.6 motion control uses input_urls if (modelId === "kling-2.6/motion-control") return "input_urls"; + // Wan 2.7 I2V uses first_frame_url (singular) + if (modelId === "wan/2-7-image-to-video") return "first_frame_url"; // Topaz video upscale uses video_url (singular) if (modelId === "topaz/video-upscale") return "video_url"; // Veo 3 models use imageUrls @@ -177,10 +263,10 @@ export function getKieImageInputKey(modelId: string): string { /** - * Detect actual image type from binary data (magic bytes) + * Detect media type from binary data (magic bytes), with fallback to declared MIME type */ -export function detectImageType(buffer: Buffer): { mimeType: string; ext: string } { - // Check magic bytes +export function detectMediaType(buffer: Buffer, declaredMimeType?: string): { mimeType: string; ext: string } { + // Check image magic bytes if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47) { return { mimeType: "image/png", ext: "png" }; } @@ -194,51 +280,72 @@ export function detectImageType(buffer: Buffer): { mimeType: string; ext: string if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46) { return { mimeType: "image/gif", ext: "gif" }; } + // Check video magic bytes (MP4: "ftyp" at offset 4) + if (buffer.length > 7 && buffer[4] === 0x66 && buffer[5] === 0x74 && buffer[6] === 0x79 && buffer[7] === 0x70) { + return { mimeType: "video/mp4", ext: "mp4" }; + } + // Check WebM magic bytes (starts with 0x1A 0x45 0xDF 0xA3 - EBML header) + if (buffer[0] === 0x1A && buffer[1] === 0x45 && buffer[2] === 0xDF && buffer[3] === 0xA3) { + return { mimeType: "video/webm", ext: "webm" }; + } + + // Fall back to declared MIME type if magic bytes didn't match + if (declaredMimeType && declaredMimeType !== "image/png") { + const extMap: Record = { + "video/mp4": "mp4", "video/webm": "webm", "video/quicktime": "mov", + "audio/mpeg": "mp3", "audio/wav": "wav", "audio/ogg": "ogg", + "image/png": "png", "image/jpeg": "jpg", "image/webp": "webp", "image/gif": "gif", + }; + const ext = extMap[declaredMimeType] || declaredMimeType.split("/")[1] || "bin"; + return { mimeType: declaredMimeType, ext }; + } + // Default to PNG return { mimeType: "image/png", ext: "png" }; } /** - * Upload a base64 image to Kie.ai and get a URL - * Required for image-to-image models since Kie doesn't accept base64 directly + * Upload a base64 media file (image, video, or audio) to Kie.ai and get a URL + * Required because Kie doesn't accept base64 directly — needs hosted URLs * Uses base64 upload endpoint (same as official Kie client) */ -export async function uploadImageToKie( +export async function uploadMediaToKie( requestId: string, apiKey: string, - base64Image: string + base64Media: string ): Promise { // Extract mime type and data from data URL let declaredMimeType = "image/png"; - let imageData = base64Image; + let mediaData = base64Media; - if (base64Image.startsWith("data:")) { - const matches = base64Image.match(/^data:([^;]+);base64,(.+)$/); + if (base64Media.startsWith("data:")) { + const matches = base64Media.match(/^data:([^;]+);base64,(.+)$/); if (matches) { declaredMimeType = matches[1]; - imageData = matches[2]; + mediaData = matches[2]; } } // Convert base64 to binary to detect actual type - const binaryData = Buffer.from(imageData, "base64"); + const binaryData = Buffer.from(mediaData, "base64"); if (binaryData.length > MAX_UPLOAD_SIZE) { - throw new Error(`[API:${requestId}] Image too large to upload (${(binaryData.length / (1024 * 1024)).toFixed(1)}MB, max ${MAX_UPLOAD_SIZE / (1024 * 1024)}MB)`); + throw new Error(`[API:${requestId}] File too large to upload (${(binaryData.length / (1024 * 1024)).toFixed(1)}MB, max ${MAX_UPLOAD_SIZE / (1024 * 1024)}MB)`); } - // Detect actual image type from magic bytes (don't trust the declared MIME type) - const detected = detectImageType(binaryData); + // Detect actual media type from magic bytes, falling back to declared MIME type + const detected = detectMediaType(binaryData, declaredMimeType); const mimeType = detected.mimeType; const ext = detected.ext; const filename = `upload_${Date.now()}.${ext}`; + const uploadPath = mimeType.startsWith("video/") ? "videos" : mimeType.startsWith("audio/") ? "audio" : "images"; - console.log(`[API:${requestId}] Uploading image to Kie.ai: ${filename} (${(binaryData.length / 1024).toFixed(1)}KB) [declared: ${declaredMimeType}, actual: ${mimeType}]`); + console.log(`[API:${requestId}] Uploading media to Kie.ai: ${filename} (${(binaryData.length / 1024).toFixed(1)}KB) [declared: ${declaredMimeType}, actual: ${mimeType}, path: ${uploadPath}]`); // Use base64 upload endpoint (same as official Kie client) // Format: data:{mime_type};base64,{data} - const dataUrl = `data:${mimeType};base64,${imageData}`; + const dataUrl = `data:${mimeType};base64,${mediaData}`; const response = await fetch("https://kieai.redpandaai.co/api/file-base64-upload", { method: "POST", @@ -248,7 +355,7 @@ export async function uploadImageToKie( }, body: JSON.stringify({ base64Data: dataUrl, - uploadPath: "images", + uploadPath, fileName: filename, }), }); @@ -274,10 +381,13 @@ export async function uploadImageToKie( throw new Error(`No download URL in upload response. Response: ${JSON.stringify(result).substring(0, 200)}`); } - console.log(`[API:${requestId}] Image uploaded: ${downloadUrl.substring(0, 80)}...`); + console.log(`[API:${requestId}] Media uploaded: ${downloadUrl.substring(0, 80)}...`); return downloadUrl; } +/** @deprecated Use uploadMediaToKie instead */ +export const uploadImageToKie = uploadMediaToKie; + /** * Poll Kie.ai task status until completion */ @@ -427,12 +537,12 @@ export async function generateWithKie( if (input.dynamicInputs) { for (const [key, value] of Object.entries(input.dynamicInputs)) { if (value !== null && value !== undefined && value !== '') { - // Check if this is an image input that needs uploading - if (typeof value === 'string' && value.startsWith('data:image')) { + // Check if this is a media input (image/video/audio) that needs uploading + if (typeof value === 'string' && value.startsWith('data:')) { // Single data URL - upload it - const url = await uploadImageToKie(requestId, apiKey, value); + const url = await uploadMediaToKie(requestId, apiKey, value); // Singular keys get a string, plural keys get an array - if (key === "image_url" || key === "video_url" || key === "tail_image_url") { + if (key === "image_url" || key === "video_url" || key === "tail_image_url" || key === "first_frame_url" || key === "last_frame_url" || key === "first_clip_url") { inputParams[key] = url; } else { inputParams[key] = [url]; @@ -442,8 +552,8 @@ export async function generateWithKie( // Array of values - check if they're data URLs that need uploading const processedArray: string[] = []; for (const item of value) { - if (typeof item === 'string' && item.startsWith('data:image')) { - const url = await uploadImageToKie(requestId, apiKey, item); + if (typeof item === 'string' && item.startsWith('data:')) { + const url = await uploadMediaToKie(requestId, apiKey, item); processedArray.push(url); } else if (typeof item === 'string' && item.startsWith('http')) { processedArray.push(item); @@ -453,7 +563,7 @@ export async function generateWithKie( } if (processedArray.length > 0) { // Singular keys get first element, plural keys get full array - if (key === "image_url" || key === "video_url" || key === "tail_image_url") { + if (key === "image_url" || key === "video_url" || key === "tail_image_url" || key === "first_frame_url" || key === "last_frame_url" || key === "first_clip_url") { inputParams[key] = processedArray[0]; } else { inputParams[key] = processedArray; @@ -476,14 +586,14 @@ export async function generateWithKie( if (image.startsWith("http")) { imageUrls.push(image); } else { - // Upload base64 image - const url = await uploadImageToKie(requestId, apiKey, image); + // Upload base64 media + const url = await uploadMediaToKie(requestId, apiKey, image); imageUrls.push(url); } } // Some models use singular string, others use arrays - if (imageKey === "image_url" || imageKey === "video_url") { + if (imageKey === "image_url" || imageKey === "video_url" || imageKey === "first_frame_url" || imageKey === "last_frame_url" || imageKey === "first_clip_url") { inputParams[imageKey] = imageUrls[0]; } else { inputParams[imageKey] = imageUrls; diff --git a/src/app/api/models/[modelId]/route.ts b/src/app/api/models/[modelId]/route.ts index 95f08f1e..7d3af573 100644 --- a/src/app/api/models/[modelId]/route.ts +++ b/src/app/api/models/[modelId]/route.ts @@ -748,6 +748,65 @@ function getKieSchema(modelId: string): ExtractedSchema { { name: "image_input", type: "image", required: false, label: "Image", isArray: true }, ], }, + "nano-banana-2": { + parameters: [ + { name: "aspect_ratio", type: "string", description: "Output aspect ratio", enum: ["1:1", "2:3", "3:2", "4:3", "16:9", "9:16", "21:9", "auto"], default: "auto" }, + { name: "resolution", type: "string", description: "Output resolution", enum: ["512", "1K", "2K", "4K"], default: "1K" }, + ], + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "image_input", type: "image", required: false, label: "Image", isArray: true }, + ], + }, + "google/imagen4": { + parameters: [ + { name: "aspect_ratio", type: "string", description: "Output aspect ratio", enum: ["1:1", "3:4", "4:3", "9:16", "16:9"], default: "1:1" }, + ], + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "google/imagen4-fast": { + parameters: [ + { name: "aspect_ratio", type: "string", description: "Output aspect ratio", enum: ["1:1", "3:4", "4:3", "9:16", "16:9"], default: "16:9" }, + { name: "num_images", type: "integer", description: "Number of images to generate", default: 1, minimum: 1, maximum: 4 }, + ], + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "google/imagen4-ultra": { + parameters: [ + { name: "aspect_ratio", type: "string", description: "Output aspect ratio", enum: ["1:1", "3:4", "4:3", "9:16", "16:9"], default: "1:1" }, + ], + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "seedream/5-lite-text-to-image": { + parameters: [ + { name: "aspect_ratio", type: "string", description: "Output aspect ratio", enum: ["1:1", "4:3", "3:4", "16:9", "9:16", "2:3", "3:2", "21:9"], default: "1:1" }, + { name: "quality", type: "string", description: "Output quality", enum: ["basic", "high"], default: "basic" }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "seedream/5-lite-image-to-image": { + parameters: [ + { name: "aspect_ratio", type: "string", description: "Output aspect ratio", enum: ["1:1", "4:3", "3:4", "16:9", "9:16", "2:3", "3:2", "21:9"], default: "1:1" }, + { name: "quality", type: "string", description: "Output quality", enum: ["basic", "high"], default: "basic" }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, + ], + }, + "wan/2-7-image": { + parameters: [ + { name: "resolution", type: "string", description: "Output resolution", enum: ["1K", "2K"], default: "2K" }, + { name: "n", type: "integer", description: "Number of images to generate", default: 4, minimum: 1, maximum: 8 }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "input_urls", type: "image", required: false, label: "Image", isArray: true }, + ], + }, "grok-imagine/text-to-image": { parameters: [ { name: "aspect_ratio", type: "string", description: "Output aspect ratio", enum: ["2:3", "3:2", "1:1", "16:9", "9:16"], default: "1:1" }, @@ -799,6 +858,56 @@ function getKieSchema(modelId: string): ExtractedSchema { inputs: [{ name: "prompt", type: "text", required: true, label: "Sound Description" }], }, // ============ Video models ============ + "bytedance/seedance-2/text-to-video": { + parameters: [ + { name: "aspect_ratio", type: "string", description: "Output aspect ratio", enum: ["16:9", "9:16", "1:1"], default: "16:9" }, + { name: "resolution", type: "string", description: "Output resolution", enum: ["720p", "1080p"], default: "720p" }, + { name: "duration", type: "integer", description: "Video duration in seconds (4-15)", default: 8, minimum: 4, maximum: 15 }, + { name: "generate_audio", type: "boolean", description: "Generate audio with the video", default: true }, + { name: "web_search", type: "boolean", description: "Enable web search for prompt enhancement", default: false }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "bytedance/seedance-2/image-to-video": { + parameters: [ + { name: "aspect_ratio", type: "string", description: "Output aspect ratio", enum: ["16:9", "9:16", "1:1"], default: "16:9" }, + { name: "resolution", type: "string", description: "Output resolution", enum: ["720p", "1080p"], default: "720p" }, + { name: "duration", type: "integer", description: "Video duration in seconds (4-15)", default: 8, minimum: 4, maximum: 15 }, + { name: "generate_audio", type: "boolean", description: "Generate audio with the video", default: true }, + { name: "web_search", type: "boolean", description: "Enable web search for prompt enhancement", default: false }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [ + { name: "prompt", type: "text", required: false, label: "Prompt" }, + { name: "first_frame_url", type: "image", required: true, label: "Image" }, + ], + }, + "bytedance/seedance-2-fast/text-to-video": { + parameters: [ + { name: "aspect_ratio", type: "string", description: "Output aspect ratio", enum: ["16:9", "9:16", "1:1"], default: "16:9" }, + { name: "resolution", type: "string", description: "Output resolution", enum: ["720p", "1080p"], default: "720p" }, + { name: "duration", type: "integer", description: "Video duration in seconds (4-15)", default: 8, minimum: 4, maximum: 15 }, + { name: "generate_audio", type: "boolean", description: "Generate audio with the video", default: true }, + { name: "web_search", type: "boolean", description: "Enable web search for prompt enhancement", default: false }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "bytedance/seedance-2-fast/image-to-video": { + parameters: [ + { name: "aspect_ratio", type: "string", description: "Output aspect ratio", enum: ["16:9", "9:16", "1:1"], default: "16:9" }, + { name: "resolution", type: "string", description: "Output resolution", enum: ["720p", "1080p"], default: "720p" }, + { name: "duration", type: "integer", description: "Video duration in seconds (4-15)", default: 8, minimum: 4, maximum: 15 }, + { name: "generate_audio", type: "boolean", description: "Generate audio with the video", default: true }, + { name: "web_search", type: "boolean", description: "Enable web search for prompt enhancement", default: false }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [ + { name: "prompt", type: "text", required: false, label: "Prompt" }, + { name: "first_frame_url", type: "image", required: true, label: "Image" }, + ], + }, "grok-imagine/text-to-video": { parameters: [ { name: "aspect_ratio", type: "string", description: "Output aspect ratio", enum: ["2:3", "3:2", "1:1", "16:9", "9:16"], default: "2:3" }, @@ -852,6 +961,43 @@ function getKieSchema(modelId: string): ExtractedSchema { { name: "video_urls", type: "image", required: true, label: "Video", isArray: true }, ], }, + "kling-3.0/video/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 in seconds", enum: ["3", "5", "10", "15"], default: "5" }, + { name: "mode", type: "string", description: "Generation mode", enum: ["std", "pro"], default: "pro" }, + { name: "sound", type: "boolean", description: "Enable sound generation", default: false }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [ + { name: "prompt", type: "text", required: false, label: "Prompt" }, + ], + }, + "kling-3.0/video/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 in seconds", enum: ["3", "5", "10", "15"], default: "5" }, + { name: "mode", type: "string", description: "Generation mode", enum: ["std", "pro"], default: "pro" }, + { name: "sound", type: "boolean", description: "Enable sound generation", default: false }, + { 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-3.0/motion-control": { + parameters: [ + { name: "mode", type: "string", description: "Output resolution", enum: ["720p", "1080p"], default: "720p" }, + { name: "character_orientation", type: "string", description: "Character orientation source", enum: ["image", "video"], default: "video" }, + { name: "background_source", type: "string", description: "Background source", enum: ["input_video", "input_image"], default: "input_video" }, + ], + inputs: [ + { name: "prompt", type: "text", required: false, label: "Prompt" }, + { name: "input_urls", type: "image", required: true, label: "Image", isArray: true }, + { name: "video_urls", type: "image", required: true, label: "Video", 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" }, @@ -897,6 +1043,35 @@ function getKieSchema(modelId: string): ExtractedSchema { { name: "image_urls", type: "image", required: true, label: "Image", isArray: true }, ], }, + "wan/2-7-text-to-video": { + parameters: [ + { name: "resolution", type: "string", description: "Output resolution", enum: ["720p", "1080p"], default: "1080p" }, + { name: "ratio", type: "string", description: "Output aspect ratio", enum: ["16:9", "9:16", "1:1", "4:3", "3:4"], default: "16:9" }, + { name: "duration", type: "integer", description: "Video duration in seconds (2-15)", default: 5, minimum: 2, maximum: 15 }, + { name: "prompt_extend", type: "boolean", description: "Enable prompt extension", default: true }, + { name: "watermark", type: "boolean", description: "Add watermark", default: false }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "negative_prompt", type: "text", required: false, label: "Negative Prompt" }, + ], + }, + "wan/2-7-image-to-video": { + parameters: [ + { name: "resolution", type: "string", description: "Output resolution", enum: ["720p", "1080p"], default: "1080p" }, + { name: "duration", type: "integer", description: "Video duration in seconds (2-15)", default: 5, minimum: 2, maximum: 15 }, + { name: "prompt_extend", type: "boolean", description: "Enable prompt extension", default: true }, + { name: "watermark", type: "boolean", description: "Add watermark", default: false }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ], + inputs: [ + { name: "prompt", type: "text", required: false, label: "Prompt" }, + { name: "negative_prompt", type: "text", required: false, label: "Negative Prompt" }, + { name: "first_frame_url", type: "image", required: true, label: "First Frame" }, + { name: "last_frame_url", type: "image", required: false, label: "Last Frame" }, + ], + }, "wan/2-6-video-to-video": { parameters: [ { name: "duration", type: "string", description: "Video duration in seconds", enum: ["5", "10"], default: "5" }, diff --git a/src/app/api/models/route.ts b/src/app/api/models/route.ts index de9b3745..8580f6fe 100644 --- a/src/app/api/models/route.ts +++ b/src/app/api/models/route.ts @@ -155,6 +155,69 @@ const KIE_MODELS: ProviderModel[] = [ coverImage: undefined, pageUrl: "https://docs.kie.ai/market/google/pro-image-to-image", }, + { + id: "nano-banana-2", + name: "Nano Banana 2 (Kie)", + description: "Google Gemini 3.1 Flash image generation via Kie.ai. Supports text-to-image and image-to-image with resolution control.", + provider: "kie", + capabilities: ["text-to-image", "image-to-image"], + coverImage: undefined, + pageUrl: "https://docs.kie.ai/market/google/nano-banana-2", + }, + { + id: "google/imagen4", + name: "Imagen 4", + description: "Google Imagen 4 high-quality text-to-image generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-image"], + coverImage: undefined, + pageUrl: "https://docs.kie.ai/market/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://docs.kie.ai/market/google/imagen4-fast", + }, + { + id: "google/imagen4-ultra", + name: "Imagen 4 Ultra", + description: "Google Imagen 4 Ultra highest-quality text-to-image generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-image"], + coverImage: undefined, + pageUrl: "https://docs.kie.ai/market/google/imagen4-ultra", + }, + { + id: "seedream/5-lite-text-to-image", + name: "Seedream 5.0 Lite", + description: "Seedream 5.0 Lite text-to-image generation via Kie.ai.", + provider: "kie", + capabilities: ["text-to-image"], + coverImage: undefined, + pageUrl: "https://docs.kie.ai/market/seedream/5-lite", + }, + { + id: "seedream/5-lite-image-to-image", + name: "Seedream 5.0 Lite Edit", + description: "Seedream 5.0 Lite image editing via Kie.ai.", + provider: "kie", + capabilities: ["image-to-image"], + coverImage: undefined, + pageUrl: "https://docs.kie.ai/market/seedream/5-lite", + }, + { + id: "wan/2-7-image", + name: "Wan 2.7 Image", + description: "Wan 2.7 image generation. Supports text-to-image and image-to-image via Kie.ai.", + provider: "kie", + capabilities: ["text-to-image", "image-to-image"], + coverImage: undefined, + pageUrl: "https://docs.kie.ai/market/wan/2-7-image", + }, { id: "grok-imagine/text-to-image", name: "Grok Imagine", @@ -173,7 +236,43 @@ const KIE_MODELS: ProviderModel[] = [ coverImage: undefined, pageUrl: "https://kie.ai/grok-imagine", }, - // ============ Video Models (11) ============ + // ============ Video Models ============ + { + id: "bytedance/seedance-2/text-to-video", + name: "Seedance 2.0", + description: "ByteDance Seedance 2.0 text-to-video generation via Kie.ai. Supports audio generation and web search.", + provider: "kie", + capabilities: ["text-to-video"], + coverImage: undefined, + pageUrl: "https://docs.kie.ai/market/bytedance/seedance-2", + }, + { + id: "bytedance/seedance-2/image-to-video", + name: "Seedance 2.0 I2V", + description: "ByteDance Seedance 2.0 image-to-video generation via Kie.ai. Supports audio generation and web search.", + provider: "kie", + capabilities: ["image-to-video"], + coverImage: undefined, + pageUrl: "https://docs.kie.ai/market/bytedance/seedance-2", + }, + { + id: "bytedance/seedance-2-fast/text-to-video", + name: "Seedance 2.0 Fast", + description: "ByteDance Seedance 2.0 Fast text-to-video generation via Kie.ai. Supports audio generation and web search.", + provider: "kie", + capabilities: ["text-to-video"], + coverImage: undefined, + pageUrl: "https://docs.kie.ai/market/bytedance/seedance-2-fast", + }, + { + id: "bytedance/seedance-2-fast/image-to-video", + name: "Seedance 2.0 Fast I2V", + description: "ByteDance Seedance 2.0 Fast image-to-video generation via Kie.ai. Supports audio generation and web search.", + provider: "kie", + capabilities: ["image-to-video"], + coverImage: undefined, + pageUrl: "https://docs.kie.ai/market/bytedance/seedance-2-fast", + }, { id: "grok-imagine/text-to-video", name: "Grok Imagine Video", @@ -221,6 +320,33 @@ const KIE_MODELS: ProviderModel[] = [ coverImage: undefined, pageUrl: "https://kie.ai/kling-2-6", }, + { + id: "kling-3.0/video/text-to-video", + name: "Kling 3.0", + description: "Kling 3.0 text-to-video generation via Kie.ai. Supports 3-15 second videos with sound.", + provider: "kie", + capabilities: ["text-to-video"], + coverImage: undefined, + pageUrl: "https://docs.kie.ai/market/kling/3-0", + }, + { + id: "kling-3.0/video/image-to-video", + name: "Kling 3.0 I2V", + description: "Kling 3.0 image-to-video generation via Kie.ai. Supports up to 2 reference images.", + provider: "kie", + capabilities: ["image-to-video"], + coverImage: undefined, + pageUrl: "https://docs.kie.ai/market/kling/3-0", + }, + { + id: "kling-3.0/motion-control", + name: "Kling 3.0 Motion Control", + description: "Kling 3.0 motion transfer from video to static image via Kie.ai.", + provider: "kie", + capabilities: ["image-to-video"], + coverImage: undefined, + pageUrl: "https://docs.kie.ai/market/kling/3-0-motion", + }, { id: "kling/v2-5-turbo-text-to-video-pro", name: "Kling 2.5 Turbo", @@ -268,6 +394,24 @@ const KIE_MODELS: ProviderModel[] = [ coverImage: undefined, pageUrl: "https://kie.ai/wan-2-6", }, + { + id: "wan/2-7-text-to-video", + name: "Wan 2.7", + description: "Wan 2.7 text-to-video generation via Kie.ai. Supports prompt extension and watermark control.", + provider: "kie", + capabilities: ["text-to-video"], + coverImage: undefined, + pageUrl: "https://docs.kie.ai/market/wan/2-7-t2v", + }, + { + id: "wan/2-7-image-to-video", + name: "Wan 2.7 I2V", + description: "Wan 2.7 image-to-video generation via Kie.ai. Supports first and last frame control.", + provider: "kie", + capabilities: ["image-to-video"], + coverImage: undefined, + pageUrl: "https://docs.kie.ai/market/wan/2-7-i2v", + }, { id: "topaz/video-upscale", name: "Topaz Video Upscale", diff --git a/src/components/nodes/ModelParameters.tsx b/src/components/nodes/ModelParameters.tsx index ec451d5a..a8a37fd4 100644 --- a/src/components/nodes/ModelParameters.tsx +++ b/src/components/nodes/ModelParameters.tsx @@ -152,6 +152,22 @@ function ModelParametersInner({ fetchSchema(); }, [modelId, provider, replicateApiKey, falApiKey, kieApiKey, wavespeedApiKey, onInputsLoaded]); + // Pre-populate schema defaults into parameters + useEffect(() => { + if (schema.length === 0) return; + const defaults: Record = {}; + let hasNewDefaults = false; + for (const param of schema) { + if (param.default !== undefined && parameters[param.name] === undefined) { + defaults[param.name] = param.default; + hasNewDefaults = true; + } + } + if (hasNewDefaults) { + onParametersChange({ ...parameters, ...defaults }); + } + }, [schema, parameters, onParametersChange]); + // Notify parent to resize node when schema loads useEffect(() => { if (schema.length > 0 && onExpandChange) {