11import { VideoIcon } from '@/components/icons'
2+ import { falVideoModels , getFalVideoModel } from '@/lib/media/fal-video-catalog'
23import { AuthMode , type BlockConfig , IntegrationType , type SubBlockConfig } from '@/blocks/types'
34import { normalizeFileInput , parseOptionalBooleanInput } from '@/blocks/utils'
45
@@ -13,24 +14,6 @@ const FALAI_PREVIOUS_MODEL_OPTIONS = [
1314 { label : 'LTXV 0.9.8' , id : 'ltxv-0.9.8' } ,
1415]
1516
16- const FALAI_LATEST_MODEL_OPTIONS = [
17- { label : 'Google Veo 3.1' , id : 'veo-3.1' } ,
18- { label : 'Google Veo 3.1 Fast' , id : 'veo-3.1-fast' } ,
19- { label : 'OpenAI Sora 2' , id : 'sora-2' } ,
20- { label : 'OpenAI Sora 2 Pro' , id : 'sora-2-pro' } ,
21- { label : 'ByteDance Seedance 2.0' , id : 'seedance-2.0' } ,
22- { label : 'ByteDance Seedance 2.0 Fast' , id : 'seedance-2.0-fast' } ,
23- { label : 'Kling 3.0 Pro' , id : 'kling-v3-pro' } ,
24- { label : 'Kling 3.0 4K' , id : 'kling-v3-4k' } ,
25- { label : 'Kling O3 Pro' , id : 'kling-o3-pro' } ,
26- { label : 'Kling O3 4K' , id : 'kling-o3-4k' } ,
27- { label : 'MiniMax Hailuo 2.3 Pro' , id : 'minimax-hailuo-2.3-pro' } ,
28- { label : 'MiniMax Hailuo 2.3 Standard' , id : 'minimax-hailuo-2.3-standard' } ,
29- { label : 'WAN 2.2 A14B Turbo' , id : 'wan-2.2-a14b-turbo' } ,
30- { label : 'LTX 2.3' , id : 'ltx-2.3' } ,
31- { label : 'LTX 2.3 Fast' , id : 'ltx-2.3-fast' } ,
32- ]
33-
3417const FALAI_VEO_MODELS = [ 'veo-3.1' , 'veo-3.1-fast' ]
3518const FALAI_SORA_MODELS = [ 'sora-2' , 'sora-2-pro' ]
3619const FALAI_SEEDANCE_STANDARD_MODELS = [ 'seedance-2.0' ]
@@ -51,23 +34,98 @@ const FALAI_AUDIO_DEFAULT_ON_MODELS = [
5134]
5235const FALAI_AUDIO_DEFAULT_OFF_MODELS = [ 'kling-o3-pro' , 'kling-o3-4k' ]
5336
54- const withFalAIModelOptions = (
55- subBlocks : SubBlockConfig [ ] ,
56- options : SubBlockConfig [ 'options' ]
57- ) : SubBlockConfig [ ] =>
58- subBlocks . map ( ( subBlock ) => {
37+ const FAL_MODEL_DEFAULT = { id : 'default' , label : 'Model default' } as const
38+ const endpointModels = falVideoModels . filter ( ( model ) => ! model . legacyId )
39+
40+ function tuningField (
41+ id : string ,
42+ title : string ,
43+ key : 'durations' | 'aspectRatios' | 'resolutions' | 'audioField' | 'promptOptimizer'
44+ ) : SubBlockConfig {
45+ return {
46+ id,
47+ title,
48+ type : 'dropdown' ,
49+ value : ( ) => FAL_MODEL_DEFAULT . id ,
50+ condition : {
51+ field : 'provider' ,
52+ value : 'falai' ,
53+ and : {
54+ field : 'model' ,
55+ value : endpointModels . filter ( ( model ) => model [ key ] ) . map ( ( model ) => model . modelId ) ,
56+ } ,
57+ } ,
58+ dependsOn : [ 'model' ] ,
59+ options : ( params ) => {
60+ const model = getFalVideoModel ( params ?. values . model )
61+ if ( ! model ?. [ key ] ) return [ ]
62+ if ( key === 'audioField' || key === 'promptOptimizer' ) {
63+ return [ FAL_MODEL_DEFAULT , { id : 'true' , label : 'On' } , { id : 'false' , label : 'Off' } ]
64+ }
65+ const duration = Number ( params ?. values . falDuration )
66+ const resolution = params ?. values . falResolution
67+ const limit =
68+ typeof resolution === 'string' ? model . maxDurationByResolution ?. [ resolution ] : undefined
69+ const choices = ( model [ key ] ?? [ ] ) . flatMap ( ( value ) => {
70+ const seconds = Number . parseFloat ( String ( value ) )
71+ if ( key === 'durations' && limit !== undefined && seconds > limit ) return [ ]
72+ if (
73+ key === 'resolutions' &&
74+ duration > ( model . maxDurationByResolution ?. [ value ] ?? Number . POSITIVE_INFINITY )
75+ )
76+ return [ ]
77+ const option = key === 'durations' ? String ( seconds ) : String ( value )
78+ return [ { id : option , label : option } ]
79+ } )
80+ return [ FAL_MODEL_DEFAULT , ...choices ]
81+ } ,
82+ }
83+ }
84+
85+ /** Separate IDs keep saved provider settings from seeding a newly selected endpoint. */
86+ const falVideoFields = [
87+ tuningField ( 'falDuration' , 'Duration (seconds)' , 'durations' ) ,
88+ tuningField ( 'falAspectRatio' , 'Aspect Ratio' , 'aspectRatios' ) ,
89+ tuningField ( 'falResolution' , 'Resolution' , 'resolutions' ) ,
90+ tuningField ( 'falGenerateAudio' , 'Generate Audio' , 'audioField' ) ,
91+ tuningField ( 'falPromptOptimizer' , 'Prompt Optimizer' , 'promptOptimizer' ) ,
92+ ]
93+
94+ function withFalVideoCatalog ( subBlocks : SubBlockConfig [ ] ) : SubBlockConfig [ ] {
95+ return subBlocks . flatMap ( ( subBlock ) => {
5996 const condition = subBlock . condition
6097 if (
61- subBlock . id === 'model' &&
62- typeof condition === 'object' &&
63- condition ?. field === 'provider' &&
64- condition . value === 'falai'
98+ typeof condition !== 'object' ||
99+ condition ?. field !== 'provider' ||
100+ condition . value !== 'falai'
65101 ) {
66- return { ... subBlock , options }
102+ return [ subBlock ]
67103 }
68-
69- return subBlock
104+ if ( subBlock . id === 'model' ) {
105+ return [
106+ {
107+ ...subBlock ,
108+ searchable : true ,
109+ options : [
110+ ...falVideoModels
111+ . map ( ( model ) => ( {
112+ id : model . modelId ,
113+ label : model . label ,
114+ description : model . endpoint ,
115+ } ) )
116+ . sort ( ( left , right ) =>
117+ left . label . localeCompare ( right . label , 'en' , { numeric : true } )
118+ ) ,
119+ { id : 'sora-2' , label : 'OpenAI Sora 2 (retired)' , hidden : true } ,
120+ { id : 'sora-2-pro' , label : 'OpenAI Sora 2 Pro (retired)' , hidden : true } ,
121+ ] ,
122+ } ,
123+ ]
124+ }
125+ if ( subBlock . id === 'apiKey' && ! condition . not ) return [ ...falVideoFields , subBlock ]
126+ return [ subBlock ]
70127 } )
128+ }
71129
72130export const VideoGeneratorBlock : BlockConfig = {
73131 type : 'video_generator' ,
@@ -1590,26 +1648,37 @@ export const VideoGeneratorV2Block: BlockConfig = {
15901648 return 'video_runway'
15911649 }
15921650 } ,
1593- params : ( params ) => ( {
1594- provider : params . provider ,
1595- apiKey : params . apiKey ,
1596- model : params . model ,
1597- endpoint : params . endpoint ,
1598- prompt : params . prompt ,
1599- duration : params . duration ? Number ( params . duration ) : undefined ,
1600- aspectRatio : params . aspectRatio ,
1601- resolution : params . resolution ,
1602- visualReference : normalizeFileInput ( params . visualReference , { single : true } ) ,
1603- consistencyMode : params . consistencyMode ,
1604- stylePreset : params . stylePreset ,
1605- promptOptimizer : parseOptionalBooleanInput ( params . promptOptimizer ) ,
1606- generateAudio : parseOptionalBooleanInput ( params . generateAudio ) ,
1607- cameraControl : params . cameraControl
1608- ? typeof params . cameraControl === 'string'
1609- ? JSON . parse ( params . cameraControl )
1610- : params . cameraControl
1611- : undefined ,
1612- } ) ,
1651+ params : ( params ) => {
1652+ const model = params . provider === 'falai' ? getFalVideoModel ( params . model ) : undefined
1653+ const useFalFields = model && ! model . legacyId
1654+ const setting = ( legacy : string , catalog : string ) => {
1655+ const value = params [ useFalFields ? catalog : legacy ]
1656+ return useFalFields && value === FAL_MODEL_DEFAULT . id ? undefined : value
1657+ }
1658+ const duration = setting ( 'duration' , 'falDuration' )
1659+ return {
1660+ provider : params . provider ,
1661+ apiKey : params . apiKey ,
1662+ model : params . model ,
1663+ endpoint : params . endpoint ,
1664+ prompt : params . prompt ,
1665+ duration : duration ? Number ( duration ) : undefined ,
1666+ aspectRatio : setting ( 'aspectRatio' , 'falAspectRatio' ) ,
1667+ resolution : setting ( 'resolution' , 'falResolution' ) ,
1668+ visualReference : normalizeFileInput ( params . visualReference , { single : true } ) ,
1669+ consistencyMode : params . consistencyMode ,
1670+ stylePreset : params . stylePreset ,
1671+ promptOptimizer : parseOptionalBooleanInput (
1672+ setting ( 'promptOptimizer' , 'falPromptOptimizer' )
1673+ ) ,
1674+ generateAudio : parseOptionalBooleanInput ( setting ( 'generateAudio' , 'falGenerateAudio' ) ) ,
1675+ cameraControl : params . cameraControl
1676+ ? typeof params . cameraControl === 'string'
1677+ ? JSON . parse ( params . cameraControl )
1678+ : params . cameraControl
1679+ : undefined ,
1680+ }
1681+ } ,
16131682 } ,
16141683 } ,
16151684 inputs : {
@@ -1671,5 +1740,22 @@ export const VideoGeneratorV3Block: BlockConfig = {
16711740 bgColor : '#181C1E' ,
16721741 icon : VideoIcon ,
16731742 hideFromToolbar : false ,
1674- subBlocks : withFalAIModelOptions ( VideoGeneratorV2Block . subBlocks , FALAI_LATEST_MODEL_OPTIONS ) ,
1743+ subBlocks : withFalVideoCatalog ( VideoGeneratorV2Block . subBlocks ) ,
1744+ inputs : {
1745+ ...VideoGeneratorV2Block . inputs ,
1746+ falDuration : {
1747+ type : 'number' ,
1748+ description : 'Duration in seconds for the selected fal endpoint' ,
1749+ } ,
1750+ falAspectRatio : { type : 'string' , description : 'Aspect ratio for the selected fal endpoint' } ,
1751+ falResolution : { type : 'string' , description : 'Resolution for the selected fal endpoint' } ,
1752+ falGenerateAudio : {
1753+ type : 'boolean' ,
1754+ description : 'Generate audio with the selected fal endpoint' ,
1755+ } ,
1756+ falPromptOptimizer : {
1757+ type : 'boolean' ,
1758+ description : 'Enable prompt optimization on the selected fal endpoint' ,
1759+ } ,
1760+ } ,
16751761}
0 commit comments