diff --git a/src/app/api/models/[modelId]/route.ts b/src/app/api/models/[modelId]/route.ts index 0973dde1..955e97a8 100644 --- a/src/app/api/models/[modelId]/route.ts +++ b/src/app/api/models/[modelId]/route.ts @@ -54,6 +54,15 @@ const IMAGE_INPUT_PATTERNS = [ "control_image", ]; +// Audio input property patterns +const AUDIO_INPUT_PATTERNS = [ + "audio_url", + "audio_urls", + "audio_input", + "audio_file", + "audio", +]; + // Text input properties const TEXT_INPUT_NAMES = ["prompt", "negative_prompt"]; @@ -192,6 +201,43 @@ function isImageInput(name: string, prop: Record, schemaCompone name.includes("_image_"); } +/** + * Check if property is an audio input based on schema type and name. + * + * Audio inputs must be strings (URLs or base64) or arrays of strings. + */ +function isAudioInput(name: string, prop: Record, schemaComponents?: Record): boolean { + const resolved = resolvePropertyType(prop, schemaComponents); + const propType = resolved.type; + if (propType !== "string" && propType !== "array") { + return false; + } + + // For arrays, check if items are strings + if (propType === "array") { + const items = prop.items as Record | undefined; + if (items && items.type && items.type !== "string") { + return false; + } + } + + // Check explicit patterns + if (AUDIO_INPUT_PATTERNS.includes(name)) { + return true; + } + + // Check description for audio-related keywords + const description = (prop.description as string || "").toLowerCase(); + if (description.includes("audio url") || + description.includes("audio file") || + description.includes("url of the audio")) { + return true; + } + + // Check name patterns + return name.endsWith("_audio") || name.startsWith("audio_"); +} + /** * Check if property is a text input */ @@ -540,6 +586,19 @@ function extractParametersFromSchema( continue; } + if (isAudioInput(name, prop, schemaComponents)) { + const resolvedType = resolvePropertyType(prop, schemaComponents).type; + inputs.push({ + name, + type: "audio", + required: required.includes(name), + label: toLabel(name), + description: prop.description as string | undefined, + isArray: resolvedType === "array", + }); + continue; + } + if (isTextInput(name)) { inputs.push({ name, @@ -568,10 +627,13 @@ function extractParametersFromSchema( return a.name.localeCompare(b.name); }); - // Sort inputs: required first, then by type (image before text), then alphabetically + // Sort inputs: required first, then by type (image, audio, text), then alphabetically + const inputTypeOrder: Record = { image: 0, audio: 1, text: 2 }; inputs.sort((a, b) => { if (a.required !== b.required) return a.required ? -1 : 1; - if (a.type !== b.type) return a.type === "image" ? -1 : 1; + const aOrder = inputTypeOrder[a.type] ?? 3; + const bOrder = inputTypeOrder[b.type] ?? 3; + if (aOrder !== bOrder) return aOrder - bOrder; return a.name.localeCompare(b.name); });