Browse Source

Merge branch 'feature/add-kie-models' into develop

handoff-20260429-1057
shrimbly 3 months ago
parent
commit
f022ca4098
  1. 168
      src/app/api/generate/providers/kie.ts
  2. 175
      src/app/api/models/[modelId]/route.ts
  3. 146
      src/app/api/models/route.ts
  4. 16
      src/components/nodes/ModelParameters.tsx

168
src/app/api/generate/providers/kie.ts

@ -39,6 +39,13 @@ export function getKieModelDefaults(modelId: string): Record<string, unknown> {
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<string, unknown> {
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<string, unknown> {
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<string, unknown> {
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<string, unknown> {
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<string, unknown> {
*/
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<string, string> = {
"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<string> {
// 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;

175
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" },

146
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",

16
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<string, unknown> = {};
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) {

Loading…
Cancel
Save