Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 239 additions & 2 deletions apps/sim/blocks/blocks/video_generator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { VideoIcon } from '@/components/icons'
import {
directVideoDefaults,
directVideoModels,
getDirectVideoModel,
} from '@/lib/media/direct-video-catalog'
import { falVideoModels, getFalVideoModel } from '@/lib/media/fal-video-catalog'
import { AuthMode, type BlockConfig, IntegrationType, type SubBlockConfig } from '@/blocks/types'
import { normalizeFileInput, parseOptionalBooleanInput } from '@/blocks/utils'
Expand Down Expand Up @@ -57,6 +62,19 @@ function tuningField(
},
dependsOn: ['model'],
options: (params) => {
if (!params) {
const values = endpointModels.flatMap((model) => {
const choices = model[key]
return Array.isArray(choices)
? choices.map((value) =>
key === 'durations' ? String(Number.parseFloat(String(value))) : String(value)
)
: choices
? ['true', 'false']
: []
})
return [FAL_MODEL_DEFAULT, ...[...new Set(values)].map((id) => ({ id, label: id }))]
}
const model = getFalVideoModel(params?.values.model)
if (!model?.[key]) return []
if (key === 'audioField' || key === 'promptOptimizer') {
Expand Down Expand Up @@ -127,6 +145,170 @@ function withFalVideoCatalog(subBlocks: SubBlockConfig[]): SubBlockConfig[] {
})
}

function selectedDirectModel(values?: Record<string, unknown>) {
const withImage = [
values?.visualReference,
values?.visualReferenceInput,
values?.visualReferenceUpload,
].some((reference) => (Array.isArray(reference) ? reference.length > 0 : Boolean(reference)))
return getDirectVideoModel(values?.provider, values?.model, withImage)
}

const directVideoFields = (
[
['duration', 'Duration (seconds)', 'durations'],
['aspectRatio', 'Aspect Ratio', 'aspectRatios'],
['resolution', 'Resolution', 'resolutions'],
['directGenerateAudio', 'Generate Audio', 'audio'],
['promptOptimizer', 'Prompt Optimizer', 'promptOptimizer'],
] as const
).map(
([id, title, key]): SubBlockConfig => ({
id,
title,
type: key === 'promptOptimizer' ? 'switch' : 'dropdown',
value: key === 'promptOptimizer' ? undefined : () => FAL_MODEL_DEFAULT.id,
dependsOn: ['provider', 'model', 'visualReferenceInput', 'visualReferenceUpload'],
condition: (values) => ({
field: 'provider',
value: selectedDirectModel(values)?.settings?.[key] ? [String(values?.provider)] : [],
}),
options: (params) => {
if (!params) {
const values = directVideoModels.flatMap((model) =>
[model.text, model.image].flatMap((settings) => {
const choices = settings?.[key]
return Array.isArray(choices) ? choices.map(String) : choices ? ['true', 'false'] : []
})
)
return [FAL_MODEL_DEFAULT, ...[...new Set(values)].map((id) => ({ id, label: id }))]
}
const model = selectedDirectModel(params?.values)
const choices = model?.settings?.[key]
if (!choices) return []
if (typeof choices === 'boolean')
return [FAL_MODEL_DEFAULT, { id: 'true', label: 'On' }, { id: 'false', label: 'Off' }]
return [
FAL_MODEL_DEFAULT,
...choices
.filter((value) => {
if (model?.protocol !== 'minimax-v1') return true
if (key === 'durations' && params?.values.resolution === '1080P') return value === 6
if (key === 'resolutions' && Number(params?.values.duration) === 10)
return value !== '1080P'
return true
})
.map((value) => ({ id: String(value), label: String(value) })),
]
},
})
)

function withDirectVideoCatalog(subBlocks: SubBlockConfig[]): SubBlockConfig[] {
return subBlocks.flatMap((field): SubBlockConfig[] => {
if (
field.id === 'model' &&
typeof field.condition === 'object' &&
field.condition.value === 'falai'
) {
const options = field.options
return [
{
...field,
options: (params) => {
const falOptions = typeof options === 'function' ? options(params) : (options ?? [])
return params
? falOptions
: [
...falOptions,
...directVideoModels.flatMap((model) => [
{ id: model.id, label: model.label },
...(model.aliases ?? []).map((id) => ({
id,
label: model.label,
hidden: true,
})),
]),
]
},
},
]
}
if (field.id === 'provider')
return [
{
...field,
options: [
{ id: 'runway', label: 'Runway' },
{ id: 'veo', label: 'Google Gemini' },
{ id: 'luma', label: 'Luma' },
{ id: 'minimax', label: 'MiniMax' },
{ id: 'falai', label: 'Fal.ai (Multi-Model)' },
],
},
...(['runway', 'veo', 'luma', 'minimax'] as const).map(
(provider): SubBlockConfig => ({
id: 'model',
title: 'Model',
type: 'dropdown',
searchable: true,
condition: { field: 'provider', value: provider },
options: directVideoModels
.filter((model) => model.provider === provider)
.flatMap((model) => [
{ id: model.id, label: model.label },
...(model.aliases ?? []).map((id) => ({ id, label: model.label, hidden: true })),
]),
value: () => directVideoDefaults[provider],
dependsOn: ['provider'],
required: true,
})
),
]
if (field.id === 'prompt') return [field, ...directVideoFields]
const condition = field.condition
const isDirect =
typeof condition === 'object' &&
condition.field === 'provider' &&
typeof condition.value === 'string' &&
condition.value !== 'falai'
if (
isDirect &&
['model', 'duration', 'aspectRatio', 'resolution', 'promptOptimizer'].includes(field.id)
)
return []
Comment thread
waleedlatif1 marked this conversation as resolved.
Resolved
if (field.id === 'endpoint')
return [
{
...field,
condition: {
field: 'provider',
value: 'minimax',
and: { field: 'model', value: ['hailuo-2.3', 'hailuo-02'] },
},
dependsOn: ['provider', 'model'],
},
]
if (field.id === 'cameraControl')
return [
{
...field,
condition: {
field: 'provider',
value: 'luma',
and: { field: 'model', value: ['ray-2', 'ray-flash-2'] },
},
dependsOn: ['provider', 'model'],
},
]
if (field.id === 'visualReferenceUpload' || field.id === 'visualReferenceInput')
return [
{ ...field, required: { field: 'model', value: ['gen4_turbo', 'gen-4', 'gen-4-turbo'] } },
]
return [field]
})
}

export const VideoGeneratorBlock: BlockConfig = {
type: 'video_generator',
name: 'Video Generator (Legacy)',
Expand Down Expand Up @@ -1733,16 +1915,71 @@ export const VideoGeneratorV3Block: BlockConfig = {
name: 'Video Generator',
description: 'Generate videos from text using AI',
longDescription:
'Generate high-quality videos from text prompts using leading AI providers. Supports Runway, Google Veo, Luma, MiniMax, and Fal.ai multi-model generation with provider-specific durations, aspect ratios, resolutions, prompt optimization, and native audio controls.',
'Generate high-quality videos from text prompts using leading AI providers. Supports Runway, Google Gemini, Luma, MiniMax, and Fal.ai multi-model generation with provider-specific durations, aspect ratios, resolutions, prompt optimization, and native audio controls.',
docsLink: 'https://docs.sim.ai/integrations/video_generator',
category: 'blocks',
integrationType: IntegrationType.AI,
bgColor: '#181C1E',
icon: VideoIcon,
hideFromToolbar: false,
subBlocks: withFalVideoCatalog(VideoGeneratorV2Block.subBlocks),
subBlocks: withDirectVideoCatalog(withFalVideoCatalog(VideoGeneratorV2Block.subBlocks)),
tools: {
...VideoGeneratorV2Block.tools,
config: {
tool: (params) => VideoGeneratorV2Block.tools.config?.tool(params) ?? 'video_runway',
params: (params) => {
if (params.provider === 'falai')
return VideoGeneratorV2Block.tools.config?.params?.(params) ?? params
const visualReference =
params.provider === 'runway'
? normalizeFileInput(params.visualReference, { single: true })
: undefined
const model = getDirectVideoModel(params.provider, params.model, Boolean(visualReference))
const setting = (key: string, supported: unknown, fallbackKey?: string) => {
if (!supported) return undefined
const value = params[key] ?? (fallbackKey ? params[fallbackKey] : undefined)
return value === FAL_MODEL_DEFAULT.id ? undefined : value
}
const duration = setting('duration', model?.settings?.durations)
return {
provider: params.provider,
model: params.model,
apiKey: params.apiKey,
prompt: params.prompt,
visualReference,
duration: duration ? Number(duration) : undefined,
aspectRatio: setting('aspectRatio', model?.settings?.aspectRatios),
resolution: setting('resolution', model?.settings?.resolutions),
generateAudio: parseOptionalBooleanInput(
setting('directGenerateAudio', model?.settings?.audio, 'generateAudio')
),
Comment thread
waleedlatif1 marked this conversation as resolved.
Resolved
promptOptimizer: parseOptionalBooleanInput(
setting('promptOptimizer', model?.settings?.promptOptimizer)
),
endpoint: model?.protocol === 'minimax-v1' ? params.endpoint : undefined,
cameraControl:
model?.protocol === 'luma-legacy' && params.cameraControl
? typeof params.cameraControl === 'string'
? JSON.parse(params.cameraControl)
: params.cameraControl
: undefined,
}
},
},
},
inputs: {
...VideoGeneratorV2Block.inputs,
duration: { type: 'number', description: 'Duration for the selected provider model' },
aspectRatio: {
type: 'string',
description: 'Frame size or aspect ratio for the selected provider model',
},
resolution: { type: 'string', description: 'Resolution for the selected provider model' },
directGenerateAudio: { type: 'boolean', description: 'Generate audio when supported' },
promptOptimizer: {
type: 'boolean',
description: 'Enable prompt optimization when supported',
},
falDuration: {
type: 'number',
description: 'Duration in seconds for the selected fal endpoint',
Expand Down
Loading
Loading