@nodaro/shared 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +32 -0
- package/dist/index.cjs +12506 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +12269 -0
- package/dist/index.d.ts +12269 -0
- package/dist/index.js +11831 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
- package/src/__tests__/__snapshots__/prompt-builder-segments.test.ts.snap +65 -0
- package/src/__tests__/ai-avatar-pricing.test.ts +321 -0
- package/src/__tests__/apply-video-negative-prompt.test.ts +76 -0
- package/src/__tests__/attach-columns.test.ts +28 -0
- package/src/__tests__/audio-crossfade-curves.test.ts +33 -0
- package/src/__tests__/build-model-menu.test.ts +58 -0
- package/src/__tests__/character-aspect-defaults.test.ts +142 -0
- package/src/__tests__/character-mention-slug.test.ts +199 -0
- package/src/__tests__/character-reference-photo-kinds.test.ts +32 -0
- package/src/__tests__/character-usage-mode.test.ts +147 -0
- package/src/__tests__/character-variant-assets.test.ts +142 -0
- package/src/__tests__/character-voice.test.ts +130 -0
- package/src/__tests__/cinematic-avatar-pricing.test.ts +119 -0
- package/src/__tests__/combine-transitions.test.ts +137 -0
- package/src/__tests__/component-types.test.ts +87 -0
- package/src/__tests__/condition-variables.test.ts +98 -0
- package/src/__tests__/credit-identifiers.test.ts +811 -0
- package/src/__tests__/default-video-provider.test.ts +63 -0
- package/src/__tests__/entity-image-handle.test.ts +43 -0
- package/src/__tests__/entity-variants.test.ts +33 -0
- package/src/__tests__/fal-lip-sync-sets.test.ts +31 -0
- package/src/__tests__/featured-entities.test.ts +33 -0
- package/src/__tests__/film-pricing.test.ts +64 -0
- package/src/__tests__/filter-condition.test.ts +285 -0
- package/src/__tests__/freecut-protocol.test.ts +35 -0
- package/src/__tests__/generate-text-items.test.ts +19 -0
- package/src/__tests__/generated-results.test.ts +50 -0
- package/src/__tests__/graph-utils.test.ts +618 -0
- package/src/__tests__/group-aggregation.test.ts +83 -0
- package/src/__tests__/i18n-locale-completeness.test.ts +156 -0
- package/src/__tests__/image-critic-node.test.ts +86 -0
- package/src/__tests__/image-mask-mode.test.ts +16 -0
- package/src/__tests__/image-ref-limit.test.ts +78 -0
- package/src/__tests__/inpaint-region.test.ts +24 -0
- package/src/__tests__/input-field-map-parity.test.ts +30 -0
- package/src/__tests__/json-evaluator.test.ts +289 -0
- package/src/__tests__/json-path.test.ts +64 -0
- package/src/__tests__/lip-sync-pricing.test.ts +142 -0
- package/src/__tests__/list-merge.test.ts +96 -0
- package/src/__tests__/list-sort.test.ts +134 -0
- package/src/__tests__/llm-models.test.ts +445 -0
- package/src/__tests__/location-mention-slug.test.ts +246 -0
- package/src/__tests__/locations-coverage-critic-schema.test.ts +92 -0
- package/src/__tests__/loop-delimiter.test.ts +39 -0
- package/src/__tests__/lottie-slots.test.ts +245 -0
- package/src/__tests__/model-catalog-flux2-resolutions.test.ts +21 -0
- package/src/__tests__/model-constants-location.test.ts +20 -0
- package/src/__tests__/model-constants.test.ts +122 -0
- package/src/__tests__/model-tree.test.ts +86 -0
- package/src/__tests__/no-internal-imports.test.ts +25 -0
- package/src/__tests__/node-default-mappings.test.ts +175 -0
- package/src/__tests__/node-mappable-fields.test.ts +7 -0
- package/src/__tests__/node-preset-export.test.ts +46 -0
- package/src/__tests__/node-preset-extract.test.ts +166 -0
- package/src/__tests__/node-refs-canonical.test.ts +54 -0
- package/src/__tests__/node-refs-combine.test.ts +54 -0
- package/src/__tests__/node-refs.test.ts +187 -0
- package/src/__tests__/node-runtime-keys.test.ts +99 -0
- package/src/__tests__/object-aspect-defaults.test.ts +59 -0
- package/src/__tests__/parameter-node-value.test.ts +137 -0
- package/src/__tests__/pinterest-url.test.ts +35 -0
- package/src/__tests__/pipeline-chat.test.ts +110 -0
- package/src/__tests__/pipeline-validation.test.ts +98 -0
- package/src/__tests__/presentation-utils.test.ts +1137 -0
- package/src/__tests__/producer-types.test.ts +47 -0
- package/src/__tests__/prompt-length-limits.test.ts +156 -0
- package/src/__tests__/provider-directive-defaults.test.ts +46 -0
- package/src/__tests__/reduce-strategy-registry.test.ts +43 -0
- package/src/__tests__/reference-board-templates.test.ts +33 -0
- package/src/__tests__/reference-roles.test.ts +36 -0
- package/src/__tests__/resolve-field-mappings.test.ts +110 -0
- package/src/__tests__/resolve-pipeline-model.test.ts +76 -0
- package/src/__tests__/scene-node-types.test.ts +80 -0
- package/src/__tests__/scraper-actors.test.ts +67 -0
- package/src/__tests__/seedance-2-catalog.test.ts +18 -0
- package/src/__tests__/seedance-2-credit-id.test.ts +23 -0
- package/src/__tests__/seedance-aspects.test.ts +52 -0
- package/src/__tests__/selector.test.ts +1151 -0
- package/src/__tests__/settled-with-limit.test.ts +214 -0
- package/src/__tests__/social-media-specs.test.ts +129 -0
- package/src/__tests__/sub-workflow-validation.test.ts +88 -0
- package/src/__tests__/switchx-pricing.test.ts +28 -0
- package/src/__tests__/to-connected-references.test.ts +153 -0
- package/src/__tests__/utilities.test.ts +541 -0
- package/src/__tests__/video-analysis-pricing.test.ts +58 -0
- package/src/__tests__/video-analysis.test.ts +63 -0
- package/src/__tests__/video-audio-capability.test.ts +204 -0
- package/src/__tests__/video-gen-providers.test.ts +28 -0
- package/src/__tests__/video-mode-aliases.test.ts +150 -0
- package/src/__tests__/video-ref-limits.test.ts +72 -0
- package/src/__tests__/video-utils.test.ts +303 -0
- package/src/__tests__/voice-changer-models.test.ts +33 -0
- package/src/__tests__/workflow-export-strip.test.ts +46 -0
- package/src/ai-avatar-pricing.ts +213 -0
- package/src/ancestor-refs.ts +48 -0
- package/src/animals.ts +231 -0
- package/src/audio-crossfade-curves.ts +73 -0
- package/src/audio-fx-presets.ts +64 -0
- package/src/avatar-payload-validators.ts +214 -0
- package/src/caption-styles.ts +31 -0
- package/src/character-aspect-defaults.ts +102 -0
- package/src/character-facets.ts +37 -0
- package/src/character-mention-slug.ts +166 -0
- package/src/character-usage-mode.ts +109 -0
- package/src/character-variant-assets.ts +121 -0
- package/src/character-voice.ts +151 -0
- package/src/cinematic-avatar-pricing.ts +111 -0
- package/src/clone-utils.ts +46 -0
- package/src/combine-transitions.ts +482 -0
- package/src/community.ts +112 -0
- package/src/component-types.ts +127 -0
- package/src/condition-variables.ts +65 -0
- package/src/credit-estimators/index.ts +15 -0
- package/src/credit-estimators/video-utils.ts +165 -0
- package/src/credit-identifiers.ts +304 -0
- package/src/entity-approval-types.ts +31 -0
- package/src/entity-aspect-defaults.ts +82 -0
- package/src/entity-asset-types.ts +296 -0
- package/src/entity-image-handle.ts +36 -0
- package/src/extra-refs.ts +139 -0
- package/src/featured-entities.ts +136 -0
- package/src/film-pricing.ts +117 -0
- package/src/filter-condition.ts +250 -0
- package/src/flux2-pricing.ts +18 -0
- package/src/freecut-protocol.ts +52 -0
- package/src/furniture.ts +167 -0
- package/src/generate-text-items.ts +15 -0
- package/src/generated-results.ts +63 -0
- package/src/group-aggregation.ts +75 -0
- package/src/hint-graph-types.ts +18 -0
- package/src/i18n/action-fx.ar.ts +89 -0
- package/src/i18n/action-fx.de.ts +89 -0
- package/src/i18n/action-fx.es.ts +89 -0
- package/src/i18n/action-fx.fr.ts +89 -0
- package/src/i18n/action-fx.he.ts +89 -0
- package/src/i18n/action-fx.hi.ts +89 -0
- package/src/i18n/action-fx.ja.ts +89 -0
- package/src/i18n/action-fx.ko.ts +89 -0
- package/src/i18n/action-fx.pt-BR.ts +89 -0
- package/src/i18n/action-fx.ru.ts +89 -0
- package/src/i18n/action-fx.zh-CN.ts +89 -0
- package/src/i18n/aesthetic.ar.ts +170 -0
- package/src/i18n/aesthetic.de.ts +48 -0
- package/src/i18n/aesthetic.es.ts +169 -0
- package/src/i18n/aesthetic.fr.ts +52 -0
- package/src/i18n/aesthetic.he.ts +169 -0
- package/src/i18n/aesthetic.hi.ts +55 -0
- package/src/i18n/aesthetic.ja.ts +165 -0
- package/src/i18n/aesthetic.ko.ts +169 -0
- package/src/i18n/aesthetic.pt-BR.ts +148 -0
- package/src/i18n/aesthetic.ru.ts +155 -0
- package/src/i18n/aesthetic.zh-CN.ts +172 -0
- package/src/i18n/animals.ar.ts +145 -0
- package/src/i18n/animals.de.ts +144 -0
- package/src/i18n/animals.es.ts +158 -0
- package/src/i18n/animals.fr.ts +144 -0
- package/src/i18n/animals.he.ts +155 -0
- package/src/i18n/animals.hi.ts +157 -0
- package/src/i18n/animals.ja.ts +159 -0
- package/src/i18n/animals.ko.ts +159 -0
- package/src/i18n/animals.pt-BR.ts +157 -0
- package/src/i18n/animals.ru.ts +155 -0
- package/src/i18n/animals.zh-CN.ts +155 -0
- package/src/i18n/atmosphere.ar.ts +49 -0
- package/src/i18n/atmosphere.de.ts +47 -0
- package/src/i18n/atmosphere.es.ts +50 -0
- package/src/i18n/atmosphere.fr.ts +47 -0
- package/src/i18n/atmosphere.he.ts +47 -0
- package/src/i18n/atmosphere.hi.ts +49 -0
- package/src/i18n/atmosphere.ja.ts +49 -0
- package/src/i18n/atmosphere.ko.ts +49 -0
- package/src/i18n/atmosphere.pt-BR.ts +49 -0
- package/src/i18n/atmosphere.ru.ts +47 -0
- package/src/i18n/atmosphere.zh-CN.ts +152 -0
- package/src/i18n/backdrop.ar.ts +53 -0
- package/src/i18n/backdrop.de.ts +46 -0
- package/src/i18n/backdrop.es.ts +54 -0
- package/src/i18n/backdrop.fr.ts +52 -0
- package/src/i18n/backdrop.he.ts +51 -0
- package/src/i18n/backdrop.hi.ts +59 -0
- package/src/i18n/backdrop.ja.ts +61 -0
- package/src/i18n/backdrop.ko.ts +59 -0
- package/src/i18n/backdrop.pt-BR.ts +59 -0
- package/src/i18n/backdrop.ru.ts +57 -0
- package/src/i18n/backdrop.zh-CN.ts +57 -0
- package/src/i18n/camera-format.ar.ts +40 -0
- package/src/i18n/camera-format.de.ts +37 -0
- package/src/i18n/camera-format.es.ts +41 -0
- package/src/i18n/camera-format.fr.ts +37 -0
- package/src/i18n/camera-format.he.ts +37 -0
- package/src/i18n/camera-format.hi.ts +42 -0
- package/src/i18n/camera-format.ja.ts +37 -0
- package/src/i18n/camera-format.ko.ts +37 -0
- package/src/i18n/camera-format.pt-BR.ts +46 -0
- package/src/i18n/camera-format.ru.ts +42 -0
- package/src/i18n/camera-format.zh-CN.ts +42 -0
- package/src/i18n/camera-motions.ar.ts +86 -0
- package/src/i18n/camera-motions.de.ts +74 -0
- package/src/i18n/camera-motions.es.ts +86 -0
- package/src/i18n/camera-motions.fr.ts +86 -0
- package/src/i18n/camera-motions.he.ts +86 -0
- package/src/i18n/camera-motions.hi.ts +99 -0
- package/src/i18n/camera-motions.ja.ts +99 -0
- package/src/i18n/camera-motions.ko.ts +99 -0
- package/src/i18n/camera-motions.pt-BR.ts +99 -0
- package/src/i18n/camera-motions.ru.ts +99 -0
- package/src/i18n/camera-motions.zh-CN.ts +99 -0
- package/src/i18n/character-fx.ar.ts +72 -0
- package/src/i18n/character-fx.de.ts +72 -0
- package/src/i18n/character-fx.es.ts +72 -0
- package/src/i18n/character-fx.fr.ts +72 -0
- package/src/i18n/character-fx.he.ts +72 -0
- package/src/i18n/character-fx.hi.ts +72 -0
- package/src/i18n/character-fx.ja.ts +72 -0
- package/src/i18n/character-fx.ko.ts +72 -0
- package/src/i18n/character-fx.pt-BR.ts +72 -0
- package/src/i18n/character-fx.ru.ts +72 -0
- package/src/i18n/character-fx.zh-CN.ts +72 -0
- package/src/i18n/color-look.ar.ts +50 -0
- package/src/i18n/color-look.de.ts +47 -0
- package/src/i18n/color-look.es.ts +50 -0
- package/src/i18n/color-look.fr.ts +50 -0
- package/src/i18n/color-look.he.ts +49 -0
- package/src/i18n/color-look.hi.ts +52 -0
- package/src/i18n/color-look.ja.ts +54 -0
- package/src/i18n/color-look.ko.ts +54 -0
- package/src/i18n/color-look.pt-BR.ts +54 -0
- package/src/i18n/color-look.ru.ts +52 -0
- package/src/i18n/color-look.zh-CN.ts +53 -0
- package/src/i18n/composition-effects.ar.ts +26 -0
- package/src/i18n/composition-effects.de.ts +25 -0
- package/src/i18n/composition-effects.es.ts +27 -0
- package/src/i18n/composition-effects.fr.ts +25 -0
- package/src/i18n/composition-effects.he.ts +25 -0
- package/src/i18n/composition-effects.hi.ts +27 -0
- package/src/i18n/composition-effects.ja.ts +27 -0
- package/src/i18n/composition-effects.ko.ts +27 -0
- package/src/i18n/composition-effects.pt-BR.ts +25 -0
- package/src/i18n/composition-effects.ru.ts +82 -0
- package/src/i18n/composition-effects.zh-CN.ts +25 -0
- package/src/i18n/era.ar.ts +37 -0
- package/src/i18n/era.de.ts +34 -0
- package/src/i18n/era.es.ts +37 -0
- package/src/i18n/era.fr.ts +37 -0
- package/src/i18n/era.he.ts +36 -0
- package/src/i18n/era.hi.ts +39 -0
- package/src/i18n/era.ja.ts +41 -0
- package/src/i18n/era.ko.ts +39 -0
- package/src/i18n/era.pt-BR.ts +41 -0
- package/src/i18n/era.ru.ts +121 -0
- package/src/i18n/era.zh-CN.ts +39 -0
- package/src/i18n/exposure-settings.ar.ts +28 -0
- package/src/i18n/exposure-settings.de.ts +26 -0
- package/src/i18n/exposure-settings.es.ts +28 -0
- package/src/i18n/exposure-settings.fr.ts +29 -0
- package/src/i18n/exposure-settings.he.ts +28 -0
- package/src/i18n/exposure-settings.hi.ts +31 -0
- package/src/i18n/exposure-settings.ja.ts +31 -0
- package/src/i18n/exposure-settings.ko.ts +31 -0
- package/src/i18n/exposure-settings.pt-BR.ts +31 -0
- package/src/i18n/exposure-settings.ru.ts +31 -0
- package/src/i18n/exposure-settings.zh-CN.ts +31 -0
- package/src/i18n/framing.ar.ts +78 -0
- package/src/i18n/framing.de.ts +73 -0
- package/src/i18n/framing.es.ts +78 -0
- package/src/i18n/framing.fr.ts +78 -0
- package/src/i18n/framing.he.ts +77 -0
- package/src/i18n/framing.hi.ts +82 -0
- package/src/i18n/framing.ja.ts +84 -0
- package/src/i18n/framing.ko.ts +82 -0
- package/src/i18n/framing.pt-BR.ts +84 -0
- package/src/i18n/framing.ru.ts +82 -0
- package/src/i18n/framing.zh-CN.ts +82 -0
- package/src/i18n/furniture.ar.ts +92 -0
- package/src/i18n/furniture.de.ts +93 -0
- package/src/i18n/furniture.es.ts +101 -0
- package/src/i18n/furniture.fr.ts +93 -0
- package/src/i18n/furniture.he.ts +101 -0
- package/src/i18n/furniture.hi.ts +101 -0
- package/src/i18n/furniture.ja.ts +101 -0
- package/src/i18n/furniture.ko.ts +101 -0
- package/src/i18n/furniture.pt-BR.ts +101 -0
- package/src/i18n/furniture.ru.ts +101 -0
- package/src/i18n/furniture.zh-CN.ts +101 -0
- package/src/i18n/held-prop.ar.ts +74 -0
- package/src/i18n/held-prop.de.ts +65 -0
- package/src/i18n/held-prop.es.ts +83 -0
- package/src/i18n/held-prop.fr.ts +74 -0
- package/src/i18n/held-prop.he.ts +73 -0
- package/src/i18n/held-prop.hi.ts +82 -0
- package/src/i18n/held-prop.ja.ts +84 -0
- package/src/i18n/held-prop.ko.ts +84 -0
- package/src/i18n/held-prop.pt-BR.ts +84 -0
- package/src/i18n/held-prop.ru.ts +82 -0
- package/src/i18n/held-prop.zh-CN.ts +82 -0
- package/src/i18n/index.ts +208 -0
- package/src/i18n/instrumentation.ar.ts +195 -0
- package/src/i18n/instrumentation.de.ts +195 -0
- package/src/i18n/instrumentation.es.ts +195 -0
- package/src/i18n/instrumentation.fr.ts +195 -0
- package/src/i18n/instrumentation.he.ts +195 -0
- package/src/i18n/instrumentation.hi.ts +195 -0
- package/src/i18n/instrumentation.ja.ts +195 -0
- package/src/i18n/instrumentation.ko.ts +195 -0
- package/src/i18n/instrumentation.pt-BR.ts +195 -0
- package/src/i18n/instrumentation.ru.ts +195 -0
- package/src/i18n/instrumentation.zh-CN.ts +195 -0
- package/src/i18n/lens.ar.ts +24 -0
- package/src/i18n/lens.de.ts +24 -0
- package/src/i18n/lens.es.ts +24 -0
- package/src/i18n/lens.fr.ts +24 -0
- package/src/i18n/lens.he.ts +24 -0
- package/src/i18n/lens.hi.ts +24 -0
- package/src/i18n/lens.ja.ts +24 -0
- package/src/i18n/lens.ko.ts +24 -0
- package/src/i18n/lens.pt-BR.ts +24 -0
- package/src/i18n/lens.ru.ts +25 -0
- package/src/i18n/lens.zh-CN.ts +24 -0
- package/src/i18n/lighting.ar.ts +86 -0
- package/src/i18n/lighting.de.ts +84 -0
- package/src/i18n/lighting.es.ts +89 -0
- package/src/i18n/lighting.fr.ts +84 -0
- package/src/i18n/lighting.he.ts +88 -0
- package/src/i18n/lighting.hi.ts +90 -0
- package/src/i18n/lighting.ja.ts +90 -0
- package/src/i18n/lighting.ko.ts +90 -0
- package/src/i18n/lighting.pt-BR.ts +90 -0
- package/src/i18n/lighting.ru.ts +88 -0
- package/src/i18n/lighting.zh-CN.ts +92 -0
- package/src/i18n/loop-subject.ar.ts +39 -0
- package/src/i18n/loop-subject.de.ts +39 -0
- package/src/i18n/loop-subject.es.ts +39 -0
- package/src/i18n/loop-subject.fr.ts +39 -0
- package/src/i18n/loop-subject.he.ts +39 -0
- package/src/i18n/loop-subject.hi.ts +39 -0
- package/src/i18n/loop-subject.ja.ts +39 -0
- package/src/i18n/loop-subject.ko.ts +39 -0
- package/src/i18n/loop-subject.pt-BR.ts +39 -0
- package/src/i18n/loop-subject.ru.ts +39 -0
- package/src/i18n/loop-subject.zh-CN.ts +39 -0
- package/src/i18n/materials.ar.ts +80 -0
- package/src/i18n/materials.de.ts +80 -0
- package/src/i18n/materials.es.ts +87 -0
- package/src/i18n/materials.fr.ts +80 -0
- package/src/i18n/materials.he.ts +86 -0
- package/src/i18n/materials.hi.ts +86 -0
- package/src/i18n/materials.ja.ts +88 -0
- package/src/i18n/materials.ko.ts +88 -0
- package/src/i18n/materials.pt-BR.ts +88 -0
- package/src/i18n/materials.ru.ts +86 -0
- package/src/i18n/materials.zh-CN.ts +86 -0
- package/src/i18n/mood.ar.ts +61 -0
- package/src/i18n/mood.de.ts +61 -0
- package/src/i18n/mood.es.ts +66 -0
- package/src/i18n/mood.fr.ts +61 -0
- package/src/i18n/mood.he.ts +64 -0
- package/src/i18n/mood.hi.ts +66 -0
- package/src/i18n/mood.ja.ts +66 -0
- package/src/i18n/mood.ko.ts +66 -0
- package/src/i18n/mood.pt-BR.ts +64 -0
- package/src/i18n/mood.ru.ts +64 -0
- package/src/i18n/mood.zh-CN.ts +64 -0
- package/src/i18n/music-genre.ar.ts +302 -0
- package/src/i18n/music-genre.de.ts +302 -0
- package/src/i18n/music-genre.es.ts +302 -0
- package/src/i18n/music-genre.fr.ts +302 -0
- package/src/i18n/music-genre.he.ts +302 -0
- package/src/i18n/music-genre.hi.ts +302 -0
- package/src/i18n/music-genre.ja.ts +302 -0
- package/src/i18n/music-genre.ko.ts +302 -0
- package/src/i18n/music-genre.pt-BR.ts +302 -0
- package/src/i18n/music-genre.ru.ts +302 -0
- package/src/i18n/music-genre.zh-CN.ts +302 -0
- package/src/i18n/music-mood.ar.ts +75 -0
- package/src/i18n/music-mood.de.ts +75 -0
- package/src/i18n/music-mood.es.ts +75 -0
- package/src/i18n/music-mood.fr.ts +75 -0
- package/src/i18n/music-mood.he.ts +75 -0
- package/src/i18n/music-mood.hi.ts +75 -0
- package/src/i18n/music-mood.ja.ts +75 -0
- package/src/i18n/music-mood.ko.ts +75 -0
- package/src/i18n/music-mood.pt-BR.ts +75 -0
- package/src/i18n/music-mood.ru.ts +75 -0
- package/src/i18n/music-mood.zh-CN.ts +75 -0
- package/src/i18n/person.ar.ts +638 -0
- package/src/i18n/person.de.ts +629 -0
- package/src/i18n/person.es.ts +645 -0
- package/src/i18n/person.fr.ts +621 -0
- package/src/i18n/person.he.ts +656 -0
- package/src/i18n/person.hi.ts +654 -0
- package/src/i18n/person.ja.ts +662 -0
- package/src/i18n/person.ko.ts +662 -0
- package/src/i18n/person.pt-BR.ts +656 -0
- package/src/i18n/person.ru.ts +656 -0
- package/src/i18n/person.zh-CN.ts +656 -0
- package/src/i18n/photo-genre.ar.ts +58 -0
- package/src/i18n/photo-genre.de.ts +52 -0
- package/src/i18n/photo-genre.es.ts +64 -0
- package/src/i18n/photo-genre.fr.ts +58 -0
- package/src/i18n/photo-genre.he.ts +62 -0
- package/src/i18n/photo-genre.hi.ts +63 -0
- package/src/i18n/photo-genre.ja.ts +65 -0
- package/src/i18n/photo-genre.ko.ts +63 -0
- package/src/i18n/photo-genre.pt-BR.ts +65 -0
- package/src/i18n/photo-genre.ru.ts +63 -0
- package/src/i18n/photo-genre.zh-CN.ts +63 -0
- package/src/i18n/photographer.ar.ts +75 -0
- package/src/i18n/photographer.de.ts +74 -0
- package/src/i18n/photographer.es.ts +77 -0
- package/src/i18n/photographer.fr.ts +77 -0
- package/src/i18n/photographer.he.ts +78 -0
- package/src/i18n/photographer.hi.ts +80 -0
- package/src/i18n/photographer.ja.ts +84 -0
- package/src/i18n/photographer.ko.ts +84 -0
- package/src/i18n/photographer.pt-BR.ts +83 -0
- package/src/i18n/photographer.ru.ts +78 -0
- package/src/i18n/photographer.zh-CN.ts +78 -0
- package/src/i18n/pose.ar.ts +95 -0
- package/src/i18n/pose.de.ts +96 -0
- package/src/i18n/pose.es.ts +104 -0
- package/src/i18n/pose.fr.ts +96 -0
- package/src/i18n/pose.he.ts +104 -0
- package/src/i18n/pose.hi.ts +104 -0
- package/src/i18n/pose.ja.ts +104 -0
- package/src/i18n/pose.ko.ts +104 -0
- package/src/i18n/pose.pt-BR.ts +104 -0
- package/src/i18n/pose.ru.ts +104 -0
- package/src/i18n/pose.zh-CN.ts +104 -0
- package/src/i18n/post-process-effects.ar.ts +25 -0
- package/src/i18n/post-process-effects.de.ts +24 -0
- package/src/i18n/post-process-effects.es.ts +26 -0
- package/src/i18n/post-process-effects.fr.ts +24 -0
- package/src/i18n/post-process-effects.he.ts +24 -0
- package/src/i18n/post-process-effects.hi.ts +26 -0
- package/src/i18n/post-process-effects.ja.ts +26 -0
- package/src/i18n/post-process-effects.ko.ts +26 -0
- package/src/i18n/post-process-effects.pt-BR.ts +24 -0
- package/src/i18n/post-process-effects.ru.ts +24 -0
- package/src/i18n/post-process-effects.zh-CN.ts +24 -0
- package/src/i18n/render-quality.ar.ts +34 -0
- package/src/i18n/render-quality.de.ts +30 -0
- package/src/i18n/render-quality.es.ts +35 -0
- package/src/i18n/render-quality.fr.ts +34 -0
- package/src/i18n/render-quality.he.ts +33 -0
- package/src/i18n/render-quality.hi.ts +39 -0
- package/src/i18n/render-quality.ja.ts +39 -0
- package/src/i18n/render-quality.ko.ts +39 -0
- package/src/i18n/render-quality.pt-BR.ts +37 -0
- package/src/i18n/render-quality.ru.ts +37 -0
- package/src/i18n/render-quality.zh-CN.ts +37 -0
- package/src/i18n/seasons.ar.ts +10 -0
- package/src/i18n/seasons.de.ts +10 -0
- package/src/i18n/seasons.es.ts +10 -0
- package/src/i18n/seasons.fr.ts +10 -0
- package/src/i18n/seasons.he.ts +10 -0
- package/src/i18n/seasons.hi.ts +10 -0
- package/src/i18n/seasons.ja.ts +10 -0
- package/src/i18n/seasons.ko.ts +10 -0
- package/src/i18n/seasons.pt-BR.ts +10 -0
- package/src/i18n/seasons.ru.ts +10 -0
- package/src/i18n/seasons.zh-CN.ts +10 -0
- package/src/i18n/setting.ar.ts +73 -0
- package/src/i18n/setting.de.ts +73 -0
- package/src/i18n/setting.es.ts +77 -0
- package/src/i18n/setting.fr.ts +73 -0
- package/src/i18n/setting.he.ts +76 -0
- package/src/i18n/setting.hi.ts +76 -0
- package/src/i18n/setting.ja.ts +78 -0
- package/src/i18n/setting.ko.ts +76 -0
- package/src/i18n/setting.pt-BR.ts +78 -0
- package/src/i18n/setting.ru.ts +76 -0
- package/src/i18n/setting.zh-CN.ts +76 -0
- package/src/i18n/style.ar.ts +55 -0
- package/src/i18n/style.de.ts +54 -0
- package/src/i18n/style.es.ts +54 -0
- package/src/i18n/style.fr.ts +54 -0
- package/src/i18n/style.he.ts +54 -0
- package/src/i18n/style.hi.ts +54 -0
- package/src/i18n/style.ja.ts +54 -0
- package/src/i18n/style.ko.ts +54 -0
- package/src/i18n/style.pt-BR.ts +56 -0
- package/src/i18n/style.ru.ts +54 -0
- package/src/i18n/style.zh-CN.ts +54 -0
- package/src/i18n/styling.ar.ts +295 -0
- package/src/i18n/styling.de.ts +285 -0
- package/src/i18n/styling.es.ts +317 -0
- package/src/i18n/styling.fr.ts +285 -0
- package/src/i18n/styling.he.ts +307 -0
- package/src/i18n/styling.hi.ts +323 -0
- package/src/i18n/styling.ja.ts +317 -0
- package/src/i18n/styling.ko.ts +311 -0
- package/src/i18n/styling.pt-BR.ts +301 -0
- package/src/i18n/styling.ru.ts +301 -0
- package/src/i18n/styling.zh-CN.ts +307 -0
- package/src/i18n/temporal.ar.ts +31 -0
- package/src/i18n/temporal.de.ts +24 -0
- package/src/i18n/temporal.es.ts +27 -0
- package/src/i18n/temporal.fr.ts +28 -0
- package/src/i18n/temporal.he.ts +31 -0
- package/src/i18n/temporal.hi.ts +31 -0
- package/src/i18n/temporal.ja.ts +31 -0
- package/src/i18n/temporal.ko.ts +31 -0
- package/src/i18n/temporal.pt-BR.ts +31 -0
- package/src/i18n/temporal.ru.ts +31 -0
- package/src/i18n/temporal.zh-CN.ts +31 -0
- package/src/i18n/transitions.ar.ts +97 -0
- package/src/i18n/transitions.de.ts +97 -0
- package/src/i18n/transitions.es.ts +97 -0
- package/src/i18n/transitions.fr.ts +97 -0
- package/src/i18n/transitions.he.ts +97 -0
- package/src/i18n/transitions.hi.ts +97 -0
- package/src/i18n/transitions.ja.ts +97 -0
- package/src/i18n/transitions.ko.ts +97 -0
- package/src/i18n/transitions.pt-BR.ts +97 -0
- package/src/i18n/transitions.ru.ts +97 -0
- package/src/i18n/transitions.zh-CN.ts +97 -0
- package/src/i18n/types.ts +158 -0
- package/src/i18n/vehicles.ar.ts +137 -0
- package/src/i18n/vehicles.de.ts +125 -0
- package/src/i18n/vehicles.es.ts +138 -0
- package/src/i18n/vehicles.fr.ts +125 -0
- package/src/i18n/vehicles.he.ts +136 -0
- package/src/i18n/vehicles.hi.ts +138 -0
- package/src/i18n/vehicles.ja.ts +138 -0
- package/src/i18n/vehicles.ko.ts +138 -0
- package/src/i18n/vehicles.pt-BR.ts +136 -0
- package/src/i18n/vehicles.ru.ts +136 -0
- package/src/i18n/vehicles.zh-CN.ts +136 -0
- package/src/i18n/voice-character.ar.ts +126 -0
- package/src/i18n/voice-character.de.ts +126 -0
- package/src/i18n/voice-character.es.ts +126 -0
- package/src/i18n/voice-character.fr.ts +126 -0
- package/src/i18n/voice-character.he.ts +126 -0
- package/src/i18n/voice-character.hi.ts +126 -0
- package/src/i18n/voice-character.ja.ts +126 -0
- package/src/i18n/voice-character.ko.ts +126 -0
- package/src/i18n/voice-character.pt-BR.ts +126 -0
- package/src/i18n/voice-character.ru.ts +126 -0
- package/src/i18n/voice-character.zh-CN.ts +126 -0
- package/src/i18n/voice-delivery.ar.ts +77 -0
- package/src/i18n/voice-delivery.de.ts +77 -0
- package/src/i18n/voice-delivery.es.ts +77 -0
- package/src/i18n/voice-delivery.fr.ts +77 -0
- package/src/i18n/voice-delivery.he.ts +77 -0
- package/src/i18n/voice-delivery.hi.ts +77 -0
- package/src/i18n/voice-delivery.ja.ts +77 -0
- package/src/i18n/voice-delivery.ko.ts +77 -0
- package/src/i18n/voice-delivery.pt-BR.ts +77 -0
- package/src/i18n/voice-delivery.ru.ts +77 -0
- package/src/i18n/voice-delivery.zh-CN.ts +77 -0
- package/src/i18n/weapons.ar.ts +115 -0
- package/src/i18n/weapons.de.ts +114 -0
- package/src/i18n/weapons.es.ts +116 -0
- package/src/i18n/weapons.fr.ts +103 -0
- package/src/i18n/weapons.he.ts +114 -0
- package/src/i18n/weapons.hi.ts +116 -0
- package/src/i18n/weapons.ja.ts +116 -0
- package/src/i18n/weapons.ko.ts +116 -0
- package/src/i18n/weapons.pt-BR.ts +114 -0
- package/src/i18n/weapons.ru.ts +114 -0
- package/src/i18n/weapons.zh-CN.ts +114 -0
- package/src/image-critic-node.ts +61 -0
- package/src/image-critic-types.ts +40 -0
- package/src/index.ts +866 -0
- package/src/inject-upstream.ts +15 -0
- package/src/inpaint-region.ts +51 -0
- package/src/json-evaluator.ts +732 -0
- package/src/json-path.ts +52 -0
- package/src/lip-sync-pricing.ts +90 -0
- package/src/list-merge.ts +40 -0
- package/src/list-sort.ts +122 -0
- package/src/list-source-resolver.ts +50 -0
- package/src/llm-models.ts +245 -0
- package/src/location-mention-slug.ts +291 -0
- package/src/location-preset-catalog-map.ts +128 -0
- package/src/loop-delimiter.ts +73 -0
- package/src/lora-routing.ts +95 -0
- package/src/lottie-overlay-catalog.ts +175 -0
- package/src/lottie-slots.ts +309 -0
- package/src/model-catalog.ts +2386 -0
- package/src/model-constants.ts +1910 -0
- package/src/model-tree.ts +120 -0
- package/src/monetization.ts +15 -0
- package/src/multi-pick.ts +42 -0
- package/src/node-default-mappings.ts +262 -0
- package/src/node-mappable-fields.ts +70 -0
- package/src/node-preset-export.ts +56 -0
- package/src/node-preset-extract.ts +147 -0
- package/src/node-refs.ts +253 -0
- package/src/node-runtime-keys.ts +117 -0
- package/src/object-aspect-defaults.ts +72 -0
- package/src/object-picker-types.ts +22 -0
- package/src/parameter-node-value.ts +284 -0
- package/src/pinterest-url.ts +28 -0
- package/src/pipeline-chat.ts +72 -0
- package/src/pipeline-defaults.ts +318 -0
- package/src/pipeline-events.ts +162 -0
- package/src/pipeline-state-types.ts +196 -0
- package/src/pipeline-types.ts +647 -0
- package/src/pipeline-validation.ts +87 -0
- package/src/presentation-types.ts +23 -0
- package/src/presentation-utils.ts +397 -0
- package/src/producer-types.ts +188 -0
- package/src/progress-curve.ts +99 -0
- package/src/provider-directive-defaults.ts +69 -0
- package/src/reduce-strategy-registry.ts +121 -0
- package/src/reference-board-templates.ts +81 -0
- package/src/reference-roles.ts +153 -0
- package/src/reference-sheet/__tests__/board-to-column.test.ts +20 -0
- package/src/reference-sheet/__tests__/catalog.test.ts +50 -0
- package/src/reference-sheet/__tests__/cost.test.ts +38 -0
- package/src/reference-sheet/__tests__/no-drift.test.ts +28 -0
- package/src/reference-sheet/__tests__/panel-plan.test.ts +61 -0
- package/src/reference-sheet/__tests__/panel-prompts.test.ts +17 -0
- package/src/reference-sheet/__tests__/plan-generation.test.ts +72 -0
- package/src/reference-sheet/__tests__/presets.test.ts +54 -0
- package/src/reference-sheet/__tests__/types.test.ts +24 -0
- package/src/reference-sheet/catalog.ts +144 -0
- package/src/reference-sheet/cost.ts +37 -0
- package/src/reference-sheet/credit-id.ts +7 -0
- package/src/reference-sheet/index.ts +8 -0
- package/src/reference-sheet/panel-plan.ts +65 -0
- package/src/reference-sheet/panel-prompts.ts +14 -0
- package/src/reference-sheet/plan-generation.ts +79 -0
- package/src/reference-sheet/presets.ts +77 -0
- package/src/reference-sheet/types.ts +92 -0
- package/src/repeat-types.ts +97 -0
- package/src/resolve-field-mappings.ts +119 -0
- package/src/route-filter.ts +80 -0
- package/src/scene-helper-types.ts +249 -0
- package/src/scene-node-types.ts +281 -0
- package/src/scraper-actors.ts +57 -0
- package/src/scraper-output-schemas.ts +25 -0
- package/src/seasons.ts +45 -0
- package/src/selector.ts +864 -0
- package/src/settled-with-limit.ts +42 -0
- package/src/shot-element.ts +43 -0
- package/src/social-media-specs.ts +121 -0
- package/src/social-post.ts +24 -0
- package/src/sub-workflow-validation.ts +78 -0
- package/src/supported-fonts.ts +40 -0
- package/src/surround.ts +113 -0
- package/src/switchx-pricing.ts +33 -0
- package/src/text-separators.ts +59 -0
- package/src/to-connected-references.ts +77 -0
- package/src/types.ts +272 -0
- package/src/variant-job-id.ts +17 -0
- package/src/vehicles.ts +213 -0
- package/src/video-analysis-pricing.ts +74 -0
- package/src/video-analysis.ts +127 -0
- package/src/video-duration.ts +25 -0
- package/src/voice-changer-models.ts +36 -0
- package/src/voice-matcher-types.ts +37 -0
- package/src/voices.ts +92 -0
- package/src/weapons.ts +190 -0
- package/src/workflow-export.ts +153 -0
|
@@ -0,0 +1,1910 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model constants shared between frontend and backend.
|
|
3
|
+
* Single source of truth for model capability sets and variable pricing rules.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod"
|
|
6
|
+
import { MODEL_CATALOG } from "./model-catalog.js"
|
|
7
|
+
|
|
8
|
+
/** Base USD cost per 1 Nodaro credit, at cost. Used for cost→credit conversion. */
|
|
9
|
+
export const CREDIT_BASE_USD = 0.02
|
|
10
|
+
|
|
11
|
+
/** Max characters for the (assembled) prompt accepted by the image-generation routes
|
|
12
|
+
* (generate-image, image-to-image, edit-image). Single source of truth — the route Zod
|
|
13
|
+
* schemas and the factory-preset guard test all read this. Prompt cap ONLY (not negativePrompt). */
|
|
14
|
+
export const IMAGE_PROMPT_MAX = 5000
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Per-provider maximum ASSEMBLED image-prompt length (chars), VERIFIED against
|
|
18
|
+
* each model's official docs.kie.ai schema (2026-06). Providers absent here use
|
|
19
|
+
* {@link IMAGE_PROMPT_MAX} (the documented KIE "standard" of 5000). Read via
|
|
20
|
+
* {@link getMaxImagePromptChars} by the prompt assembler's truncation AND the
|
|
21
|
+
* frontend over-limit warning — the data-driven replacement for the old
|
|
22
|
+
* hardcoded 2000 that silently severed appended cinematography hints + the
|
|
23
|
+
* `Avoid:` negative on long prompts.
|
|
24
|
+
*
|
|
25
|
+
* Both directions matter: some models accept FAR MORE than 5000 (nano-banana-2 /
|
|
26
|
+
* gpt-image-2-i2i = 20000) and some FAR LESS (seedream-5-lite = 1000) — sending
|
|
27
|
+
* 5000 to a 1000-char model is a provider-side rejection. Only list a value you
|
|
28
|
+
* can quote from the official schema; the t2i / i2i / edit siblings of one family
|
|
29
|
+
* genuinely differ (verified: qwen t2i 3000 vs qwen-i2i 5000 vs qwen-edit 2000),
|
|
30
|
+
* so never copy a sibling's number.
|
|
31
|
+
*
|
|
32
|
+
* Truncation is a graceful safety net, never a hard reject — the editor warns the
|
|
33
|
+
* user BEFORE submit (switch model / trim) and the assembler truncates if they
|
|
34
|
+
* proceed. So the image routes stay generous (a single high ceiling) and the
|
|
35
|
+
* per-model enforcement lives here.
|
|
36
|
+
*/
|
|
37
|
+
export const MAX_IMAGE_PROMPT_CHARS_BY_PROVIDER: Record<string, number> = {
|
|
38
|
+
// ── higher than the 5000 default ──
|
|
39
|
+
"nano-banana-2": 20000, // docs.kie.ai/market/google/nano-banana-2
|
|
40
|
+
"nano-banana-pro": 20000, // docs.kie.ai/market/google/pro-image-to-image
|
|
41
|
+
"gpt-image-2-i2i": 20000, // docs.kie.ai/market/gpt/gpt-image-2-image-to-image
|
|
42
|
+
// ── lower than the 5000 default (over-send risk if left at default) ──
|
|
43
|
+
"seedream": 3000, // docs.kie.ai/market/seedream/4-5-text-to-image
|
|
44
|
+
"seedream-edit": 3000, // docs.kie.ai/market/seedream/4-5-edit
|
|
45
|
+
"seedream-5-lite": 1000, // docs.kie.ai/market/seedream/5-lite-text-to-image
|
|
46
|
+
"seedream-5-lite-i2i": 3000, // docs.kie.ai/market/seedream-5-lite-image-to-image (NB: t2i sibling is 1000)
|
|
47
|
+
"qwen": 3000, // docs.kie.ai/market/qwen/text-to-image
|
|
48
|
+
"qwen-edit": 2000, // docs.kie.ai/market/qwen/image-edit
|
|
49
|
+
// verified == 5000 default (no entry needed): imagen4(-fast/-ultra), nano-banana,
|
|
50
|
+
// nano-banana-edit, flux, flux-flex, gpt-image-2, ideogram-v3/-edit/-remix,
|
|
51
|
+
// z-image, grok, qwen-i2i.
|
|
52
|
+
// grok-i2i: doc states 390000 (78× its t2i sibling) — treated as a KIE schema
|
|
53
|
+
// typo and left at the 5000 default per the sanity-cap decision.
|
|
54
|
+
// UNVERIFIED (no limit stated in schema) → 5000 default: flux-kontext(-max)
|
|
55
|
+
// gpt-image, gpt-image-i2i, flux-i2i, flux-pro-i2i, ideogram-reframe.
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Max assembled image-prompt length (chars) for a provider: its verified
|
|
59
|
+
* override from {@link MAX_IMAGE_PROMPT_CHARS_BY_PROVIDER}, else the
|
|
60
|
+
* {@link IMAGE_PROMPT_MAX} default. Single source of truth shared by the prompt
|
|
61
|
+
* assembler, the over-limit warning, and the image-route ceiling. */
|
|
62
|
+
export function getMaxImagePromptChars(provider: string | undefined): number {
|
|
63
|
+
return (provider && MAX_IMAGE_PROMPT_CHARS_BY_PROVIDER[provider]) || IMAGE_PROMPT_MAX
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Video prompt/negative cap shared by routes + editor (MCP verbs already use
|
|
68
|
+
* 8000). Providers allow far more (KIE seedance-2: 20k chars) — the old
|
|
69
|
+
* per-route 2500 was a false-positive blocker for legitimate multi-shot
|
|
70
|
+
* Seedance prompts.
|
|
71
|
+
*/
|
|
72
|
+
export const VIDEO_PROMPT_MAX = 8000
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Suno prompt / lyrics / content ceiling — the LARGEST any Suno version accepts
|
|
76
|
+
* in custom mode (V4.5 / V4.5PLUS / V4.5ALL / V5 / V5.5 = 5000). The route Zod
|
|
77
|
+
* uses this as a generous ceiling; the handler clamps to the per-version cap via
|
|
78
|
+
* {@link getMaxSunoPromptChars} (V4/V3.5 = 3000, non-custom = 500). Shared with
|
|
79
|
+
* the editor `maxLength` / counter (warn-don't-block at the per-version cap).
|
|
80
|
+
* `style` and `title` have their own caps ({@link getMaxSunoStyleChars} /
|
|
81
|
+
* {@link SUNO_TITLE_MAX}).
|
|
82
|
+
*/
|
|
83
|
+
export const SUNO_TEXT_MAX = 5000
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Absolute ceiling for the `prompt` / `negativePrompt` fields on the image and
|
|
87
|
+
* video routes' Zod schemas. The PER-MODEL limits below (and the editor warning)
|
|
88
|
+
* do the real work; the routes stay generous so they never hard-reject a legit
|
|
89
|
+
* long prompt — the assembler/payload truncates to the model cap as a graceful
|
|
90
|
+
* safety net (warn-don't-block). 20000 = the largest verified per-model image/
|
|
91
|
+
* video limit (seedance-2 / nano-banana-2 / gemini-omni). Anything past this is
|
|
92
|
+
* abuse, not a real prompt.
|
|
93
|
+
*/
|
|
94
|
+
export const PROMPT_HARD_CEILING = 20000
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Per-provider maximum VIDEO prompt length (chars), VERIFIED against each model's
|
|
98
|
+
* official docs.kie.ai schema (2026-06). Absent → {@link VIDEO_PROMPT_MAX} (8000)
|
|
99
|
+
* default (used for models whose schema states NO limit: veo3 family, kling-3.0,
|
|
100
|
+
* kling-3-omni, runway-aleph). Read via {@link getMaxVideoPromptChars}.
|
|
101
|
+
*
|
|
102
|
+
* Most video models cap WELL BELOW the old flat 8000 (kling 2.6 = 1000!), so the
|
|
103
|
+
* 8000 route value was over-sending to nearly every provider. A few accept much
|
|
104
|
+
* more (seedance-2 = 20000). Mode note: where a model's t2v and i2v schemas
|
|
105
|
+
* differ, the SMALLER is used so neither mode over-sends (hailuo-2.3-pro: t2v
|
|
106
|
+
* 1500 / i2v 5000 → 1500).
|
|
107
|
+
*/
|
|
108
|
+
export const MAX_VIDEO_PROMPT_CHARS_BY_PROVIDER: Record<string, number> = {
|
|
109
|
+
// Kling family — our `kling`→kling-2.6 (1000), `kling-turbo`→v2.5-turbo-pro (2500)
|
|
110
|
+
"kling": 1000,
|
|
111
|
+
"kling-turbo": 2500,
|
|
112
|
+
"kling-master": 5000,
|
|
113
|
+
// higher than the 8000 default
|
|
114
|
+
"seedance-2": 20000,
|
|
115
|
+
"seedance-2-fast": 20000,
|
|
116
|
+
"seedance-2-mini": 20000,
|
|
117
|
+
"seedance-2-extend": 20000,
|
|
118
|
+
"gemini-omni-video": 20000,
|
|
119
|
+
"bytedance-lite": 10000,
|
|
120
|
+
"bytedance-pro": 10000,
|
|
121
|
+
"bytedance-pro-fast": 10000,
|
|
122
|
+
// lower than the 8000 default
|
|
123
|
+
"minimax": 1500,
|
|
124
|
+
"hailuo-standard": 1500,
|
|
125
|
+
"hailuo-2.3-pro": 1500, // t2v 1500 / i2v 5000 → smaller, safe for both modes
|
|
126
|
+
"hailuo-2.3": 5000,
|
|
127
|
+
"seedance": 2500, // Seedance 1.5 Pro
|
|
128
|
+
"wan": 5000,
|
|
129
|
+
"wan-i2v": 5000,
|
|
130
|
+
"wan-turbo": 5000,
|
|
131
|
+
"wan-flash": 1500,
|
|
132
|
+
"wan-videoedit": 5000,
|
|
133
|
+
"wan-2.7-i2v": 5000,
|
|
134
|
+
"wan-2.7-t2v": 5000,
|
|
135
|
+
"grok-i2v": 5000,
|
|
136
|
+
"grok-imagine-video-1.5": 4096,
|
|
137
|
+
"happyhorse": 5000,
|
|
138
|
+
"happyhorse-i2v": 5000,
|
|
139
|
+
"happyhorse-ref2v": 5000,
|
|
140
|
+
"happyhorse-edit": 5000,
|
|
141
|
+
"runway-kie": 1800,
|
|
142
|
+
// NB: `wan-2.7` / `wan-2.7-pro` are IMAGE models (see the image registry), not
|
|
143
|
+
// video. The motion-transfer route's prompt is capped at the motion-control
|
|
144
|
+
// limit (2500) inside the route itself — its provider values are the shared
|
|
145
|
+
// `kling` (→1000 here, safely tighter) / `kling-3.0` ids, not a "motion-*" id.
|
|
146
|
+
// UNVERIFIED (schema states no limit) → 8000 default: veo3, veo3.1, veo3_lite
|
|
147
|
+
// kling-3.0, kling-3-omni, runway-aleph.
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Max video-prompt length (chars) for a provider: verified override, else
|
|
151
|
+
* {@link VIDEO_PROMPT_MAX}. Shared by the video routes/payload truncation and
|
|
152
|
+
* the editor over-limit warning. */
|
|
153
|
+
export function getMaxVideoPromptChars(provider: string | undefined): number {
|
|
154
|
+
return (provider && MAX_VIDEO_PROMPT_CHARS_BY_PROVIDER[provider]) || VIDEO_PROMPT_MAX
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Default negative-prompt cap when a provider has no specific verified limit. */
|
|
158
|
+
export const NEGATIVE_PROMPT_MAX = 5000
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Per-provider native `negative_prompt` cap (chars), VERIFIED from docs.kie.ai.
|
|
162
|
+
* Only matters for providers that send the negative as a NATIVE param
|
|
163
|
+
* ({@link NATIVE_NEGATIVE_PROMPT_MODELS} / {@link NATIVE_NEGATIVE_VIDEO_PROVIDERS});
|
|
164
|
+
* everywhere else the negative is folded into the prompt as `Avoid: …` and rides
|
|
165
|
+
* the prompt cap. Absent → {@link NEGATIVE_PROMPT_MAX}.
|
|
166
|
+
*/
|
|
167
|
+
export const MAX_NEGATIVE_PROMPT_CHARS_BY_PROVIDER: Record<string, number> = {
|
|
168
|
+
// image
|
|
169
|
+
"imagen4": 5000, "imagen4-fast": 5000, "imagen4-ultra": 5000,
|
|
170
|
+
"ideogram-v3": 500, "ideogram-remix": 5000,
|
|
171
|
+
"qwen": 500, "qwen-i2i": 500, "qwen-edit": 500,
|
|
172
|
+
// video (native-negative families)
|
|
173
|
+
"kling-master": 500,
|
|
174
|
+
"kling-turbo": 500, // i2v 500 / t2v 2500 → smaller, safe for both modes
|
|
175
|
+
"wan-2.7-i2v": 500, "wan-2.7-t2v": 500, "wan-videoedit": 500,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Max native negative-prompt length (chars) for a provider. */
|
|
179
|
+
export function getMaxNegativePromptChars(provider: string | undefined): number {
|
|
180
|
+
return (provider && MAX_NEGATIVE_PROMPT_CHARS_BY_PROVIDER[provider]) || NEGATIVE_PROMPT_MAX
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Default TTS text cap (legacy `elevenlabs`) when no per-model override. */
|
|
184
|
+
export const TTS_TEXT_MAX = 5000
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Per-model Text-to-Speech character cap (PER REQUEST), from official ElevenLabs
|
|
188
|
+
* docs. turbo/multilingual accept FAR more than the old flat 5000; v3 uses the
|
|
189
|
+
* conservative 3000 (official pages say 5000 but the API is widely reported to
|
|
190
|
+
* hard-limit v3 at 3000 — conservative avoids prod rejections). Absent →
|
|
191
|
+
* {@link TTS_TEXT_MAX}.
|
|
192
|
+
*/
|
|
193
|
+
export const MAX_TTS_CHARS_BY_PROVIDER: Record<string, number> = {
|
|
194
|
+
"elevenlabs-turbo": 40000, // == eleven_flash_v2_5 (functionally equivalent)
|
|
195
|
+
"elevenlabs-multilingual": 10000, // eleven_multilingual_v2
|
|
196
|
+
"elevenlabs-v3": 3000, // conservative (official 5000 / API-reported 3000)
|
|
197
|
+
"elevenlabs-dialogue": 2000, // text-to-dialogue recommended per-request max
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Max TTS text length (chars) for a provider: verified override, else {@link TTS_TEXT_MAX}. */
|
|
201
|
+
export function getMaxTtsChars(provider: string | undefined): number {
|
|
202
|
+
return (provider && MAX_TTS_CHARS_BY_PROVIDER[provider]) || TTS_TEXT_MAX
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Suno per-version field caps (from docs.kie.ai/suno-api/generate-music). The old
|
|
207
|
+
* flat {@link SUNO_TEXT_MAX} (3000) was simultaneously too low for V4.5+/V5
|
|
208
|
+
* prompts (5000) and too high for `style` (1000) and `title` (80).
|
|
209
|
+
* - prompt / lyrics: 500 in non-custom mode (all versions); in custom mode
|
|
210
|
+
* 3000 for V4/V3.5 and 5000 for V4.5 / V4.5PLUS / V4.5ALL / V5 / V5.5.
|
|
211
|
+
* - style: 200 for V4/V3.5, 1000 for V4.5+.
|
|
212
|
+
* - title: 80 (all versions).
|
|
213
|
+
*/
|
|
214
|
+
export const SUNO_TITLE_MAX = 80
|
|
215
|
+
|
|
216
|
+
/** Max Suno `prompt` (= lyrics in custom mode) length for a model version. */
|
|
217
|
+
export function getMaxSunoPromptChars(model: string | undefined, customMode: boolean): number {
|
|
218
|
+
if (!customMode) return 500
|
|
219
|
+
return model === "V4" || model === "V3_5" ? 3000 : 5000
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Max Suno `style` length for a model version. */
|
|
223
|
+
export function getMaxSunoStyleChars(model: string | undefined): number {
|
|
224
|
+
return model === "V4" || model === "V3_5" ? 200 : 1000
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Models that accept negative_prompt as a native API parameter.
|
|
228
|
+
// All other models get negative prompt appended to the prompt text as "Avoid: ...".
|
|
229
|
+
export const NATIVE_NEGATIVE_PROMPT_MODELS = new Set([
|
|
230
|
+
"imagen4", "imagen4-fast", "imagen4-ultra",
|
|
231
|
+
"ideogram-remix", "ideogram-v3",
|
|
232
|
+
"qwen", "qwen-edit",
|
|
233
|
+
])
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Video providers that accept `negative_prompt` natively in their KIE.ai
|
|
237
|
+
* request payload. Everywhere else, the helper below appends `Avoid: <text>`
|
|
238
|
+
* to the prompt instead so the negative intent still reaches the model.
|
|
239
|
+
*
|
|
240
|
+
* Sourced from inline comments in `backend/src/providers/kie/video.ts`
|
|
241
|
+
* (Kling family + Wan family) plus the wan-s2v speech-to-video flow.
|
|
242
|
+
*
|
|
243
|
+
* NOT included (KIE will silently drop the param):
|
|
244
|
+
* minimax / hailuo-* family, veo3.* family, sora2 / sora2-pro,
|
|
245
|
+
* bytedance-* family, grok / grok-i2v, seedance-* family,
|
|
246
|
+
* happyhorse-*, ltx-* family, runway-kie, wan-animate-move/replace
|
|
247
|
+
* (Wan Animate is a separate model from regular Wan and doesn't take
|
|
248
|
+
* negative_prompt — verified against KIE docs 2026-05-28).
|
|
249
|
+
*/
|
|
250
|
+
export const NATIVE_NEGATIVE_VIDEO_PROVIDERS = new Set<string>([
|
|
251
|
+
// Kling family
|
|
252
|
+
"kling", "kling-turbo", "kling-master", "kling-3.0", "kling-3-omni",
|
|
253
|
+
// Wan family (regular Wan, NOT wan-animate)
|
|
254
|
+
"wan", "wan-flash", "wan-videoedit",
|
|
255
|
+
"wan-i2v", "wan-turbo",
|
|
256
|
+
"wan-2.7-i2v", "wan-2.7-t2v",
|
|
257
|
+
// Wan speech-to-video
|
|
258
|
+
"wan-s2v",
|
|
259
|
+
])
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Apply the negative prompt to a video-provider request.
|
|
263
|
+
*
|
|
264
|
+
* If the provider natively accepts `negative_prompt` (Kling / Wan families),
|
|
265
|
+
* the helper returns the original prompt and the trimmed negative as
|
|
266
|
+
* `nativeNegativePrompt` — the caller should forward it as the API's
|
|
267
|
+
* dedicated field.
|
|
268
|
+
*
|
|
269
|
+
* Otherwise, the helper appends `Avoid: <negativePrompt>` to the prompt
|
|
270
|
+
* (consistent with `buildImagePrompt` in `prompt-builder.ts`) so the model
|
|
271
|
+
* still sees the negative intent, and returns `nativeNegativePrompt = undefined`.
|
|
272
|
+
*
|
|
273
|
+
* Empty / missing negative is a no-op.
|
|
274
|
+
*
|
|
275
|
+
* Naming mirrors the established image-side pattern. Keep the wording
|
|
276
|
+
* identical to `prompt-builder.ts` ("Avoid: ...") so users moving between
|
|
277
|
+
* image and video nodes see consistent behavior.
|
|
278
|
+
*/
|
|
279
|
+
export function applyVideoNegativePrompt(
|
|
280
|
+
prompt: string | undefined,
|
|
281
|
+
negativePrompt: string | undefined,
|
|
282
|
+
provider: string,
|
|
283
|
+
): { prompt: string | undefined; nativeNegativePrompt: string | undefined } {
|
|
284
|
+
// This is the universal "finalize the video prompt for the provider" chokepoint
|
|
285
|
+
// (every KIE video method, the extend route, the orchestrator, and the editor
|
|
286
|
+
// preview route through it). So it ALSO clamps to the model's verified caps —
|
|
287
|
+
// a graceful safety net (the editor warns the user first; warn-don't-block).
|
|
288
|
+
// Under-cap prompts (the common case) pass through byte-identical.
|
|
289
|
+
const promptMax = getMaxVideoPromptChars(provider)
|
|
290
|
+
const clamp = (p: string | undefined): string | undefined =>
|
|
291
|
+
p != null && p.length > promptMax ? p.slice(0, promptMax) : p
|
|
292
|
+
|
|
293
|
+
const neg = negativePrompt?.trim()
|
|
294
|
+
if (!neg) return { prompt: clamp(prompt), nativeNegativePrompt: undefined }
|
|
295
|
+
if (NATIVE_NEGATIVE_VIDEO_PROVIDERS.has(provider)) {
|
|
296
|
+
// Native negative rides its own param → clamp each independently.
|
|
297
|
+
return { prompt: clamp(prompt), nativeNegativePrompt: neg.slice(0, getMaxNegativePromptChars(provider)) }
|
|
298
|
+
}
|
|
299
|
+
// Non-native: fold the negative into the prompt as "Avoid: …". Reserve room for
|
|
300
|
+
// the suffix so a long base prompt can't sever the negative (mirror the image
|
|
301
|
+
// assembler), then clamp the whole to the model cap.
|
|
302
|
+
const base = prompt && prompt.trim().length > 0 ? prompt : ""
|
|
303
|
+
if (!base) return { prompt: `Avoid: ${neg}`.slice(0, promptMax), nativeNegativePrompt: undefined }
|
|
304
|
+
const avoid = `\nAvoid: ${neg}`
|
|
305
|
+
const room = Math.max(0, promptMax - avoid.length)
|
|
306
|
+
const injected = `${base.slice(0, room)}${avoid}`.slice(0, promptMax)
|
|
307
|
+
return { prompt: injected, nativeNegativePrompt: undefined }
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Image providers that natively use reference images in their API.
|
|
311
|
+
// Used by `buildImagePrompt` to filter referenceImageUrls before sending,
|
|
312
|
+
// and by the frontend to warn when the user has `{image:N:label}` tokens
|
|
313
|
+
// in a prompt for a provider that would silently ignore them.
|
|
314
|
+
//
|
|
315
|
+
// Verified against backend/src/providers/kie/models.ts (inputType +
|
|
316
|
+
// imageParam) and the i2i flow in backend/src/providers/kie/image.ts.
|
|
317
|
+
//
|
|
318
|
+
// Image providers that natively use reference images in their API.
|
|
319
|
+
//
|
|
320
|
+
// Two categories included:
|
|
321
|
+
// 1. Direct ref support: nano-banana family (multi-ref T2I) + flux-kontext
|
|
322
|
+
// (single image edit) + all i2i/edit variants.
|
|
323
|
+
// 2. Auto-switched: T2I models that have an i2i sibling — when the user
|
|
324
|
+
// attaches refs to one of these in a generate-image node, the backend
|
|
325
|
+
// route silently routes to the i2i sibling. See `T2I_TO_I2I_VARIANT`.
|
|
326
|
+
// User-facing benefit: pick GPT Image / Grok / Qwen / Seedream / Flux at
|
|
327
|
+
// the dropdown, attach refs, and they "just work".
|
|
328
|
+
//
|
|
329
|
+
// Excluded (no ref support and no i2i sibling — refs would be useless):
|
|
330
|
+
// imagen4, imagen4-fast, imagen4-ultra, ideogram-v3, z-image
|
|
331
|
+
export const MODELS_WITH_REFERENCE_IMAGE_SUPPORT = new Set([
|
|
332
|
+
// Multi-reference text-to-image
|
|
333
|
+
"nano-banana",
|
|
334
|
+
"nano-banana-pro",
|
|
335
|
+
"nano-banana-2",
|
|
336
|
+
// T2I providers that auto-route to their i2i sibling when refs are attached
|
|
337
|
+
"gpt-image",
|
|
338
|
+
"gpt-image-2",
|
|
339
|
+
"grok",
|
|
340
|
+
"qwen",
|
|
341
|
+
"seedream",
|
|
342
|
+
"seedream-5-lite",
|
|
343
|
+
"flux",
|
|
344
|
+
"flux-flex",
|
|
345
|
+
// Image editing / image-to-image (reference = source image)
|
|
346
|
+
"nano-banana-edit",
|
|
347
|
+
"gpt-image-i2i",
|
|
348
|
+
"gpt-image-2-i2i",
|
|
349
|
+
"flux-i2i",
|
|
350
|
+
"flux-pro-i2i",
|
|
351
|
+
"flux-kontext",
|
|
352
|
+
"flux-kontext-max",
|
|
353
|
+
"ideogram-edit",
|
|
354
|
+
"ideogram-remix",
|
|
355
|
+
"ideogram-reframe",
|
|
356
|
+
"qwen-i2i",
|
|
357
|
+
"qwen-edit",
|
|
358
|
+
"seedream-edit",
|
|
359
|
+
"seedream-5-lite-i2i",
|
|
360
|
+
"grok-i2i",
|
|
361
|
+
// Upscale / background ops (source acts as the reference)
|
|
362
|
+
"recraft-remove-bg",
|
|
363
|
+
"recraft-upscale",
|
|
364
|
+
"topaz-image-upscale",
|
|
365
|
+
// Wan 2.7 — accepts up to 9 optional ref images via input_urls (pure T2I when omitted)
|
|
366
|
+
"wan-2.7",
|
|
367
|
+
// Replicate "Open" (uncensored) image models
|
|
368
|
+
"flux-2-klein",
|
|
369
|
+
"kontext-multi",
|
|
370
|
+
"flux-2-pro",
|
|
371
|
+
"flux-2-max",
|
|
372
|
+
])
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* T2I provider → i2i sibling. When a generate-image node carries reference
|
|
376
|
+
* images and the chosen provider is in this map, the backend route
|
|
377
|
+
* transparently routes to the i2i variant so the refs are actually used.
|
|
378
|
+
*
|
|
379
|
+
* The user keeps seeing the T2I name in the UI; the warning component shows
|
|
380
|
+
* an info hint that the i2i variant is being used under the hood.
|
|
381
|
+
*/
|
|
382
|
+
export const T2I_TO_I2I_VARIANT: Record<string, string> = {
|
|
383
|
+
"gpt-image": "gpt-image-i2i",
|
|
384
|
+
"gpt-image-2": "gpt-image-2-i2i",
|
|
385
|
+
"grok": "grok-i2i",
|
|
386
|
+
"qwen": "qwen-i2i",
|
|
387
|
+
"seedream": "seedream-edit",
|
|
388
|
+
"seedream-5-lite": "seedream-5-lite-i2i",
|
|
389
|
+
"flux": "flux-pro-i2i",
|
|
390
|
+
"flux-flex": "flux-i2i",
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Maximum number of reference images each provider accepts.
|
|
395
|
+
* Sourced from the corresponding KIE.ai endpoint's documented input array
|
|
396
|
+
* size, or from the model's natural input shape (single-source i2i = 1).
|
|
397
|
+
*
|
|
398
|
+
* Providers absent from this map default to `DEFAULT_REF_IMAGE_MAX` (4).
|
|
399
|
+
* Providers absent from `MODELS_WITH_REFERENCE_IMAGE_SUPPORT` ignore
|
|
400
|
+
* reference images entirely regardless of this value.
|
|
401
|
+
*/
|
|
402
|
+
export const REF_IMAGE_MAX_LIMITS: Record<string, number> = {
|
|
403
|
+
// Multi-reference T2I (Nano Banana family is the only T2I family that
|
|
404
|
+
// actually accepts ref URLs via the KIE wrapper's `image_input` param).
|
|
405
|
+
"nano-banana": 8,
|
|
406
|
+
"nano-banana-pro": 8,
|
|
407
|
+
"nano-banana-2": 4,
|
|
408
|
+
"wan-2.7": 9,
|
|
409
|
+
// Image-to-image (multi-source array)
|
|
410
|
+
"nano-banana-edit": 8,
|
|
411
|
+
"gpt-image-i2i": 16,
|
|
412
|
+
"gpt-image-2-i2i": 16,
|
|
413
|
+
"flux-i2i": 4,
|
|
414
|
+
"flux-pro-i2i": 4,
|
|
415
|
+
"seedream-edit": 16,
|
|
416
|
+
"seedream-5-lite-i2i": 16,
|
|
417
|
+
// Single-source i2i (one input image)
|
|
418
|
+
"flux-kontext": 1,
|
|
419
|
+
"flux-kontext-max": 1,
|
|
420
|
+
"ideogram-edit": 1,
|
|
421
|
+
"ideogram-remix": 1,
|
|
422
|
+
"ideogram-reframe": 1,
|
|
423
|
+
"qwen-i2i": 1,
|
|
424
|
+
"qwen-edit": 1,
|
|
425
|
+
"grok-i2i": 1,
|
|
426
|
+
"recraft-remove-bg": 1,
|
|
427
|
+
"recraft-upscale": 1,
|
|
428
|
+
"topaz-image-upscale": 1,
|
|
429
|
+
// Replicate Open models — Klein takes an optional single ref; Kontext Multi
|
|
430
|
+
// (multi-image-kontext-pro) is a two-image combiner (input_image_1/2 only);
|
|
431
|
+
// Flux 2 Pro up to 4; Max up to 8.
|
|
432
|
+
"flux-2-klein": 1,
|
|
433
|
+
"kontext-multi": 2,
|
|
434
|
+
"flux-2-pro": 4,
|
|
435
|
+
"flux-2-max": 8,
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export const DEFAULT_REF_IMAGE_MAX = 4
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Maximum reference images the UI should advertise for an IMAGE model — the
|
|
442
|
+
* scalar image analogue of the video side's `videoReferenceLimits` (images have
|
|
443
|
+
* a single reference kind, so a scalar suffices). Studio's Framing picker reads
|
|
444
|
+
* this for its "References" chip; the editor's reference-handle popover reads it
|
|
445
|
+
* too (via `getHandleConnectionLimit`).
|
|
446
|
+
*
|
|
447
|
+
* Returns 0 when the model accepts no reference images at all (so callers can
|
|
448
|
+
* hide the chip / disable the handle), else the per-model cap from
|
|
449
|
+
* {@link REF_IMAGE_MAX_LIMITS} (fallback {@link DEFAULT_REF_IMAGE_MAX}).
|
|
450
|
+
* Therefore `imageReferenceLimit(p) > 0` is an exact "supports references" gate.
|
|
451
|
+
*
|
|
452
|
+
* Two behaviors keep the count matching what a user actually gets:
|
|
453
|
+
* 1. T2I → i2i resolution. Pickers list text-to-image ids (grok, gpt-image-2,
|
|
454
|
+
* seedream-5-lite, flux, …). When references are attached the generate-image
|
|
455
|
+
* route silently routes to the i2i sibling ({@link T2I_TO_I2I_VARIANT}) —
|
|
456
|
+
* the endpoint that actually consumes refs — so the advertised cap is the
|
|
457
|
+
* SIBLING's (grok→grok-i2i = 1, gpt-image-2→gpt-image-2-i2i = 16), never the
|
|
458
|
+
* t2i id's absent default. Mirrors the route's `resolveEffectiveProvider`.
|
|
459
|
+
* 2. {@link REF_IMAGE_MAX_LIMITS} is the PRODUCT/UI cap, which may be
|
|
460
|
+
* intentionally TIGHTER than the raw provider schema (e.g. flux-2-pro = 4
|
|
461
|
+
* here though the BFL schema accepts 8; flux-2-klein = 1 though the array
|
|
462
|
+
* slices at 5). The user-facing count must reflect the product cap, so the
|
|
463
|
+
* reader reads this map verbatim — do NOT "fix" these to the schema maxima.
|
|
464
|
+
*/
|
|
465
|
+
export function imageReferenceLimit(provider: string | undefined): number {
|
|
466
|
+
if (!provider || !MODELS_WITH_REFERENCE_IMAGE_SUPPORT.has(provider)) return 0
|
|
467
|
+
const effective = T2I_TO_I2I_VARIANT[provider] ?? provider
|
|
468
|
+
return REF_IMAGE_MAX_LIMITS[effective] ?? DEFAULT_REF_IMAGE_MAX
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Variable pricing: which setting type affects cost per provider
|
|
472
|
+
export const VARIABLE_PRICING_MODELS: Record<string, "quality" | "resolution" | "rendering-speed"> = {
|
|
473
|
+
"gpt-image": "quality",
|
|
474
|
+
"gpt-image-i2i": "quality",
|
|
475
|
+
"gpt-image-2": "resolution",
|
|
476
|
+
"gpt-image-2-i2i": "resolution",
|
|
477
|
+
"nano-banana-pro": "resolution",
|
|
478
|
+
"nano-banana-2": "resolution",
|
|
479
|
+
"flux": "resolution",
|
|
480
|
+
"flux-flex": "resolution",
|
|
481
|
+
"flux-i2i": "resolution",
|
|
482
|
+
"flux-pro-i2i": "resolution",
|
|
483
|
+
"seedream": "quality",
|
|
484
|
+
"seedream-edit": "quality",
|
|
485
|
+
"seedream-5-lite": "quality",
|
|
486
|
+
"seedream-5-lite-i2i": "quality",
|
|
487
|
+
"topaz-image-upscale": "resolution",
|
|
488
|
+
"ideogram-edit": "rendering-speed",
|
|
489
|
+
"ideogram-remix": "rendering-speed",
|
|
490
|
+
"ideogram-reframe": "rendering-speed",
|
|
491
|
+
"ideogram-v3": "rendering-speed",
|
|
492
|
+
"wan-2.7": "resolution",
|
|
493
|
+
"wan-2.7-pro": "resolution",
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
// Models where quality=high triggers composite credit identifier
|
|
498
|
+
export const HIGH_QUALITY_PROVIDERS = new Set(["gpt-image", "gpt-image-i2i", "seedream", "seedream-edit", "seedream-5-lite", "seedream-5-lite-i2i"])
|
|
499
|
+
|
|
500
|
+
// Models where resolution=2K triggers composite credit identifier
|
|
501
|
+
export const TWO_K_RESOLUTION_PROVIDERS = new Set(["flux", "flux-pro-i2i", "flux-flex", "flux-i2i"])
|
|
502
|
+
|
|
503
|
+
// Models where both 2K and 4K resolutions trigger composite credit identifiers (1K is base)
|
|
504
|
+
export const RESOLUTION_2K_4K_TIERED_PROVIDERS = new Set([
|
|
505
|
+
"nano-banana-2",
|
|
506
|
+
"gpt-image-2",
|
|
507
|
+
"gpt-image-2-i2i",
|
|
508
|
+
"wan-2.7",
|
|
509
|
+
"wan-2.7-pro",
|
|
510
|
+
])
|
|
511
|
+
|
|
512
|
+
// Ideogram family models with TURBO/QUALITY pricing variants
|
|
513
|
+
export const IDEOGRAM_PROVIDERS = new Set(["ideogram-edit", "ideogram-remix", "ideogram-reframe", "ideogram-v3"])
|
|
514
|
+
|
|
515
|
+
// =====================================================================
|
|
516
|
+
// Provider arrays (single source of truth for route Zod validation)
|
|
517
|
+
// =====================================================================
|
|
518
|
+
|
|
519
|
+
/** Text-to-image providers (no input image required) */
|
|
520
|
+
export const IMAGE_GEN_PROVIDERS = [
|
|
521
|
+
"nano-banana",
|
|
522
|
+
"flux",
|
|
523
|
+
"nano-banana-pro",
|
|
524
|
+
"nano-banana-2",
|
|
525
|
+
"grok",
|
|
526
|
+
"gpt-image",
|
|
527
|
+
"gpt-image-2",
|
|
528
|
+
"imagen4",
|
|
529
|
+
"imagen4-fast",
|
|
530
|
+
"imagen4-ultra",
|
|
531
|
+
"ideogram-v3",
|
|
532
|
+
"qwen",
|
|
533
|
+
"seedream",
|
|
534
|
+
"seedream-5-lite",
|
|
535
|
+
"flux-flex",
|
|
536
|
+
"flux-kontext",
|
|
537
|
+
"flux-kontext-max",
|
|
538
|
+
"z-image",
|
|
539
|
+
"wan-2.7",
|
|
540
|
+
"wan-2.7-pro",
|
|
541
|
+
// Replicate Open (uncensored) — runs through Replicate, not KIE
|
|
542
|
+
"flux-2-klein",
|
|
543
|
+
"flux-2-pro",
|
|
544
|
+
"flux-2-max",
|
|
545
|
+
] as const
|
|
546
|
+
|
|
547
|
+
/** Image-to-image providers (require input image) */
|
|
548
|
+
export const IMAGE_I2I_PROVIDERS = [
|
|
549
|
+
"nano-banana",
|
|
550
|
+
"nano-banana-2",
|
|
551
|
+
"nano-banana-pro",
|
|
552
|
+
"grok-i2i",
|
|
553
|
+
"flux-i2i",
|
|
554
|
+
"flux-pro-i2i",
|
|
555
|
+
"gpt-image-i2i",
|
|
556
|
+
"gpt-image-2-i2i",
|
|
557
|
+
"ideogram-edit",
|
|
558
|
+
"ideogram-remix",
|
|
559
|
+
"ideogram-reframe",
|
|
560
|
+
"qwen-i2i",
|
|
561
|
+
"qwen-edit",
|
|
562
|
+
"seedream-edit",
|
|
563
|
+
"seedream-5-lite-i2i",
|
|
564
|
+
"flux-kontext",
|
|
565
|
+
"flux-kontext-max",
|
|
566
|
+
// Replicate Open (uncensored) — multi-image Kontext via Replicate
|
|
567
|
+
"kontext-multi",
|
|
568
|
+
// BFL Flux 2 Pro — runs through Replicate with safety_tolerance=5 (max for Pro)
|
|
569
|
+
"flux-2-pro",
|
|
570
|
+
// BFL Flux 2 Max — runs through Replicate with safety_tolerance=5, up to 8 refs
|
|
571
|
+
"flux-2-max",
|
|
572
|
+
] as const
|
|
573
|
+
|
|
574
|
+
/** Image editing providers (upscale, remove bg, etc.) */
|
|
575
|
+
export const IMAGE_EDIT_PROVIDERS = [
|
|
576
|
+
"recraft-upscale",
|
|
577
|
+
"recraft-remove-bg",
|
|
578
|
+
"nano-banana-edit",
|
|
579
|
+
"topaz-image-upscale",
|
|
580
|
+
// grok-upscale takes a prior Grok generation's task_id (NOT an image URL) —
|
|
581
|
+
// see edit-image route for the taskId-vs-imageUrl branching.
|
|
582
|
+
"grok-upscale",
|
|
583
|
+
] as const
|
|
584
|
+
|
|
585
|
+
/** Modify image providers (I2I + edit-with-prompt) */
|
|
586
|
+
export const MODIFY_IMAGE_PROVIDERS = [
|
|
587
|
+
...IMAGE_I2I_PROVIDERS,
|
|
588
|
+
"nano-banana-edit",
|
|
589
|
+
] as const
|
|
590
|
+
export type ModifyImageProvider = typeof MODIFY_IMAGE_PROVIDERS[number]
|
|
591
|
+
|
|
592
|
+
/** Image upscale providers */
|
|
593
|
+
export const UPSCALE_IMAGE_PROVIDERS = [
|
|
594
|
+
"recraft-upscale",
|
|
595
|
+
"topaz-image-upscale",
|
|
596
|
+
] as const
|
|
597
|
+
export type UpscaleImageProvider = typeof UPSCALE_IMAGE_PROVIDERS[number]
|
|
598
|
+
|
|
599
|
+
// Derived types from provider arrays
|
|
600
|
+
export type ImageGenProvider = typeof IMAGE_GEN_PROVIDERS[number]
|
|
601
|
+
export type ImageI2IProvider = typeof IMAGE_I2I_PROVIDERS[number]
|
|
602
|
+
export type ImageEditProvider = typeof IMAGE_EDIT_PROVIDERS[number]
|
|
603
|
+
|
|
604
|
+
/** Image-to-video providers */
|
|
605
|
+
export const IMAGE_TO_VIDEO_PROVIDERS = [
|
|
606
|
+
"minimax",
|
|
607
|
+
"veo3",
|
|
608
|
+
"veo3.1",
|
|
609
|
+
"veo3_lite",
|
|
610
|
+
"kling",
|
|
611
|
+
"kling-turbo",
|
|
612
|
+
"kling-3.0",
|
|
613
|
+
"kling-master",
|
|
614
|
+
"seedance",
|
|
615
|
+
"seedance-2",
|
|
616
|
+
"seedance-2-fast",
|
|
617
|
+
"seedance-2-mini",
|
|
618
|
+
"hailuo-2.3-pro",
|
|
619
|
+
"hailuo-2.3",
|
|
620
|
+
"hailuo-standard",
|
|
621
|
+
"wan-i2v",
|
|
622
|
+
"wan-turbo",
|
|
623
|
+
"bytedance-lite",
|
|
624
|
+
"bytedance-pro",
|
|
625
|
+
"bytedance-pro-fast",
|
|
626
|
+
"grok-i2v",
|
|
627
|
+
"wan-2.7-i2v",
|
|
628
|
+
"happyhorse-i2v",
|
|
629
|
+
"happyhorse-ref2v",
|
|
630
|
+
"runway-kie",
|
|
631
|
+
"kling-3-omni",
|
|
632
|
+
"gemini-omni-video",
|
|
633
|
+
"ltx-2.3-pro",
|
|
634
|
+
"ltx-2.3-fast",
|
|
635
|
+
// xAI Grok Imagine Video 1.5 — KIE i2v (image_urls required). Also listed in
|
|
636
|
+
// TEXT_TO_VIDEO_PROVIDERS so it's offered everywhere in the unified node; the
|
|
637
|
+
// t2v route guards no-image runs via VIDEO_PROVIDERS_REQUIRING_IMAGE.
|
|
638
|
+
"grok-imagine-video-1.5",
|
|
639
|
+
// Replicate disabled
|
|
640
|
+
// "runway",
|
|
641
|
+
// "pika",
|
|
642
|
+
// "sora",
|
|
643
|
+
] as const
|
|
644
|
+
export type ImageToVideoProvider = typeof IMAGE_TO_VIDEO_PROVIDERS[number]
|
|
645
|
+
|
|
646
|
+
/** Text-to-video providers */
|
|
647
|
+
export const TEXT_TO_VIDEO_PROVIDERS = [
|
|
648
|
+
"minimax",
|
|
649
|
+
"veo3",
|
|
650
|
+
"veo3.1",
|
|
651
|
+
"veo3_lite",
|
|
652
|
+
"kling",
|
|
653
|
+
"kling-turbo",
|
|
654
|
+
"kling-3.0",
|
|
655
|
+
"grok",
|
|
656
|
+
"seedance",
|
|
657
|
+
"seedance-2",
|
|
658
|
+
"seedance-2-fast",
|
|
659
|
+
"seedance-2-mini",
|
|
660
|
+
"wan",
|
|
661
|
+
"hailuo-standard",
|
|
662
|
+
"bytedance-lite",
|
|
663
|
+
"bytedance-pro",
|
|
664
|
+
"wan-turbo",
|
|
665
|
+
"wan-2.7-t2v",
|
|
666
|
+
"happyhorse",
|
|
667
|
+
"runway-kie",
|
|
668
|
+
"gemini-omni-video",
|
|
669
|
+
"ltx-2.3-pro",
|
|
670
|
+
"ltx-2.3-fast",
|
|
671
|
+
// ── i2v-only providers (image required) ──────────────────────────────────
|
|
672
|
+
// These have NO text-to-video mode, but the unified Generate Video node can
|
|
673
|
+
// dispatch ANY of its providers down the t2v path when no image is wired.
|
|
674
|
+
// They must pass this enum so /v1/text-to-video can return the clean
|
|
675
|
+
// "requires an input image" 400 (VIDEO_PROVIDERS_REQUIRING_IMAGE — derived
|
|
676
|
+
// from MODEL_CATALOG modes) instead of an opaque Zod validation error.
|
|
677
|
+
// They never reach a provider. Guarded by the dispatch-totality test in
|
|
678
|
+
// __tests__/video-mode-aliases.test.ts.
|
|
679
|
+
"grok-imagine-video-1.5",
|
|
680
|
+
"kling-3-omni",
|
|
681
|
+
"kling-master",
|
|
682
|
+
"hailuo-2.3",
|
|
683
|
+
"hailuo-2.3-pro",
|
|
684
|
+
"bytedance-pro-fast",
|
|
685
|
+
"happyhorse-ref2v",
|
|
686
|
+
// Replicate disabled
|
|
687
|
+
// "runway",
|
|
688
|
+
// "pika",
|
|
689
|
+
// "sora",
|
|
690
|
+
] as const
|
|
691
|
+
export type TextToVideoProvider = typeof TEXT_TO_VIDEO_PROVIDERS[number]
|
|
692
|
+
|
|
693
|
+
/** Unified video-generation providers (image-to-video ∪ text-to-video) for the generate-video node. */
|
|
694
|
+
export const VIDEO_GEN_PROVIDERS = [
|
|
695
|
+
...IMAGE_TO_VIDEO_PROVIDERS,
|
|
696
|
+
...TEXT_TO_VIDEO_PROVIDERS.filter(
|
|
697
|
+
(p): p is typeof TEXT_TO_VIDEO_PROVIDERS[number] =>
|
|
698
|
+
!(IMAGE_TO_VIDEO_PROVIDERS as readonly string[]).includes(p),
|
|
699
|
+
),
|
|
700
|
+
] as const
|
|
701
|
+
export type VideoGenProvider = typeof VIDEO_GEN_PROVIDERS[number]
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Video models that expose DISTINCT provider ids per mode (text-to-video vs
|
|
705
|
+
* image-to-video) but represent ONE user-facing model. KIE keys i2v and t2v off
|
|
706
|
+
* different ids in different maps (KIE_VIDEO_MODELS vs KIE_TEXT_TO_VIDEO_MODELS),
|
|
707
|
+
* so each id only resolves to a real model in its native mode.
|
|
708
|
+
*
|
|
709
|
+
* The unified Generate Video node shows ONE picker row (the `base` id) and
|
|
710
|
+
* auto-selects the mode by whether an input image is present; execution remaps
|
|
711
|
+
* base→mode id via {@link resolveVideoProviderForMode}. This registry is the
|
|
712
|
+
* single source of truth shared by the picker collapse (frontend
|
|
713
|
+
* `model-options.ts`), the frontend DAG executor (`execute-node.ts`), and the
|
|
714
|
+
* backend orchestrator (`payload-builder.ts`) so they can't drift.
|
|
715
|
+
*
|
|
716
|
+
* `base` MUST be one of the group's own mode ids (kept = the i2v id, since the
|
|
717
|
+
* unified picker's i2v entries win on collision). Honesty invariants are guarded
|
|
718
|
+
* in `__tests__/video-mode-aliases.test.ts`.
|
|
719
|
+
*/
|
|
720
|
+
export interface VideoModeAlias {
|
|
721
|
+
/** Canonical id stored on the node + shown in the unified picker. */
|
|
722
|
+
base: string
|
|
723
|
+
/** KIE id to use when an input image is present (image-to-video). */
|
|
724
|
+
i2v: string
|
|
725
|
+
/** KIE id to use for pure text-to-video. */
|
|
726
|
+
t2v: string
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
export const VIDEO_MODE_ALIASES: readonly VideoModeAlias[] = [
|
|
730
|
+
{ base: "grok-i2v", i2v: "grok-i2v", t2v: "grok" },
|
|
731
|
+
{ base: "wan-i2v", i2v: "wan-i2v", t2v: "wan" },
|
|
732
|
+
{ base: "wan-2.7-i2v", i2v: "wan-2.7-i2v", t2v: "wan-2.7-t2v" },
|
|
733
|
+
// HappyHorse: happyhorse/image-to-video + happyhorse/text-to-video are mode
|
|
734
|
+
// twins of one KIE model family — previously TWO unified-picker rows, and the
|
|
735
|
+
// i2v row crashed the t2v route when run without an image.
|
|
736
|
+
{ base: "happyhorse-i2v", i2v: "happyhorse-i2v", t2v: "happyhorse" },
|
|
737
|
+
] as const
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Resolve a (possibly base / cross-mode) video provider id to the concrete KIE
|
|
741
|
+
* id for the given execution mode. Accepts any member id of an alias group
|
|
742
|
+
* (base, i2v, or t2v) so existing workflows that stored either mode's id keep
|
|
743
|
+
* working. Non-aliased providers (single-id models, VEO, Replicate, etc.) pass
|
|
744
|
+
* through unchanged.
|
|
745
|
+
*/
|
|
746
|
+
export function resolveVideoProviderForMode(
|
|
747
|
+
provider: string,
|
|
748
|
+
mode: "image-to-video" | "text-to-video",
|
|
749
|
+
): string {
|
|
750
|
+
for (const g of VIDEO_MODE_ALIASES) {
|
|
751
|
+
if (provider === g.base || provider === g.i2v || provider === g.t2v) {
|
|
752
|
+
return mode === "image-to-video" ? g.i2v : g.t2v
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return provider
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* t2v twin ids hidden from the unified Generate Video picker — the i2v/base
|
|
760
|
+
* entry already represents both modes (execution remaps by image presence).
|
|
761
|
+
* Only includes twins whose `base` is NOT the t2v id, so the surviving picker
|
|
762
|
+
* entry is the base. Consumed by the frontend `VIDEO_GEN_MODELS` collapse.
|
|
763
|
+
*/
|
|
764
|
+
export const VIDEO_GEN_COLLAPSED_T2V_IDS: ReadonlySet<string> = new Set(
|
|
765
|
+
VIDEO_MODE_ALIASES.filter((g) => g.t2v !== g.base).map((g) => g.t2v),
|
|
766
|
+
)
|
|
767
|
+
|
|
768
|
+
/** Video-to-video providers */
|
|
769
|
+
export const VIDEO_TO_VIDEO_PROVIDERS = [
|
|
770
|
+
"wan",
|
|
771
|
+
"wan-flash",
|
|
772
|
+
"wan-videoedit",
|
|
773
|
+
"luma-modify",
|
|
774
|
+
"runway-aleph",
|
|
775
|
+
"happyhorse-edit",
|
|
776
|
+
] as const
|
|
777
|
+
export type VideoToVideoProvider = typeof VIDEO_TO_VIDEO_PROVIDERS[number]
|
|
778
|
+
|
|
779
|
+
/** Face swap providers */
|
|
780
|
+
export const FACE_SWAP_PROVIDERS = [
|
|
781
|
+
"roop",
|
|
782
|
+
] as const
|
|
783
|
+
export type FaceSwapProvider = typeof FACE_SWAP_PROVIDERS[number]
|
|
784
|
+
|
|
785
|
+
/** Video upscale providers */
|
|
786
|
+
export const VIDEO_UPSCALE_PROVIDERS = [
|
|
787
|
+
"topaz",
|
|
788
|
+
"veo-1080p",
|
|
789
|
+
"veo-4k",
|
|
790
|
+
] as const
|
|
791
|
+
export type VideoUpscaleProvider = typeof VIDEO_UPSCALE_PROVIDERS[number]
|
|
792
|
+
|
|
793
|
+
/** Extend video providers */
|
|
794
|
+
export const EXTEND_VIDEO_PROVIDERS = [
|
|
795
|
+
"veo-extend",
|
|
796
|
+
"runway-extend",
|
|
797
|
+
"ltx-2.3-pro",
|
|
798
|
+
"seedance-2-extend",
|
|
799
|
+
] as const
|
|
800
|
+
export type ExtendVideoProvider = typeof EXTEND_VIDEO_PROVIDERS[number]
|
|
801
|
+
|
|
802
|
+
/** Lip sync providers */
|
|
803
|
+
export const LIP_SYNC_PROVIDERS = [
|
|
804
|
+
"kling-avatar",
|
|
805
|
+
"kling-avatar-pro",
|
|
806
|
+
"infinitalk",
|
|
807
|
+
// ByteDance OmniHuman 1.5 — KIE image+audio avatar, prompt-directed
|
|
808
|
+
// performance, per-second billed, 60s audio cap. Routes the default KIE
|
|
809
|
+
// path (not in any replicate/fal/seedance/video-input set).
|
|
810
|
+
"omnihuman-1-5",
|
|
811
|
+
"latentsync",
|
|
812
|
+
"wav2lip",
|
|
813
|
+
"video-retalking",
|
|
814
|
+
"sadtalker",
|
|
815
|
+
// HeyGen Lipsync Precision + Sync Lipsync 2 Pro — Replicate-hosted, video-input
|
|
816
|
+
// dubbing models billed per second of output. Routed through replicateLipSync;
|
|
817
|
+
// priced per-second via buildLipSyncCreditId (see lip-sync-pricing.ts).
|
|
818
|
+
"heygen-lipsync-precision",
|
|
819
|
+
"lipsync-2-pro",
|
|
820
|
+
// Sync Lipsync v3 — fal.ai-hosted, video-input dubbing model billed per
|
|
821
|
+
// second of output (/min). Routed through falLipSync (the worker's fal
|
|
822
|
+
// branch); priced per-second via buildLipSyncCreditId (see lip-sync-pricing.ts).
|
|
823
|
+
"sync-lipsync-v3",
|
|
824
|
+
// Volcengine video-to-video lip sync — KIE-hosted, VIDEO-input AI dubbing
|
|
825
|
+
// (mode lite/basic; multi-speaker scene-detection + speaker ID in basic).
|
|
826
|
+
// Billed per-second via buildLipSyncCreditId; routed through the KIE
|
|
827
|
+
// `lipSyncVideo` path (KieModelConfig.inputKind === "video"), NOT the
|
|
828
|
+
// image+prompt `lipSync` path.
|
|
829
|
+
"volcengine-lipsync",
|
|
830
|
+
// Seedance 2 / 2 Fast — not "lip-sync models" per se, but ByteDance's
|
|
831
|
+
// multimodal video models do native phoneme-level lip sync in 8+
|
|
832
|
+
// languages when fed `reference_audio_urls` alongside a `first_frame_url`.
|
|
833
|
+
// Routed through the i2v provider with the audio passed as a reference,
|
|
834
|
+
// not the dedicated lip-sync flow.
|
|
835
|
+
"seedance-2",
|
|
836
|
+
"seedance-2-fast",
|
|
837
|
+
"seedance-2-mini",
|
|
838
|
+
] as const
|
|
839
|
+
export type LipSyncProvider = typeof LIP_SYNC_PROVIDERS[number]
|
|
840
|
+
|
|
841
|
+
/** Seedance variants exposed via the lip-sync surface. They go through
|
|
842
|
+
* the i2v provider with the audio plumbed as `reference_audio_urls`. */
|
|
843
|
+
export const SEEDANCE_LIP_SYNC_PROVIDERS = new Set<string>([
|
|
844
|
+
"seedance-2",
|
|
845
|
+
"seedance-2-fast",
|
|
846
|
+
"seedance-2-mini",
|
|
847
|
+
])
|
|
848
|
+
|
|
849
|
+
/** Replicate-based lip-sync providers (video or image+audio via Replicate SDK) */
|
|
850
|
+
export const REPLICATE_LIP_SYNC_PROVIDERS = new Set([
|
|
851
|
+
"latentsync",
|
|
852
|
+
"wav2lip",
|
|
853
|
+
"video-retalking",
|
|
854
|
+
"sadtalker",
|
|
855
|
+
"heygen-lipsync-precision",
|
|
856
|
+
"lipsync-2-pro",
|
|
857
|
+
] as const)
|
|
858
|
+
|
|
859
|
+
/** fal.ai-based lip-sync providers (video+audio → video via the fal queue API).
|
|
860
|
+
* Routed through `falLipSync` in the worker's `handleLipSync` fal branch. */
|
|
861
|
+
export const FAL_LIP_SYNC_PROVIDERS = new Set([
|
|
862
|
+
"sync-lipsync-v3",
|
|
863
|
+
] as const)
|
|
864
|
+
|
|
865
|
+
/** Lip-sync providers that require video input (not image) */
|
|
866
|
+
export const VIDEO_INPUT_LIP_SYNC_PROVIDERS = new Set([
|
|
867
|
+
"latentsync",
|
|
868
|
+
"video-retalking",
|
|
869
|
+
"heygen-lipsync-precision",
|
|
870
|
+
"lipsync-2-pro",
|
|
871
|
+
"sync-lipsync-v3",
|
|
872
|
+
"volcengine-lipsync",
|
|
873
|
+
] as const)
|
|
874
|
+
|
|
875
|
+
/** Lip-sync providers that accept either video or image input */
|
|
876
|
+
export const FLEXIBLE_INPUT_LIP_SYNC_PROVIDERS = new Set([
|
|
877
|
+
"wav2lip",
|
|
878
|
+
] as const)
|
|
879
|
+
|
|
880
|
+
/** Standard aspect ratio → pixel dimensions for composition nodes */
|
|
881
|
+
export const ASPECT_RATIO_DIMENSIONS: Record<string, { width: number; height: number }> = {
|
|
882
|
+
"16:9": { width: 1920, height: 1080 },
|
|
883
|
+
"9:16": { width: 1080, height: 1920 },
|
|
884
|
+
"1:1": { width: 1080, height: 1080 },
|
|
885
|
+
"4:5": { width: 1080, height: 1350 },
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/** Motion transfer providers */
|
|
889
|
+
export const MOTION_TRANSFER_PROVIDERS = [
|
|
890
|
+
"kling",
|
|
891
|
+
"kling-3.0",
|
|
892
|
+
"wan-animate-move",
|
|
893
|
+
"wan-animate-replace",
|
|
894
|
+
] as const
|
|
895
|
+
export type MotionTransferProviderType = typeof MOTION_TRANSFER_PROVIDERS[number]
|
|
896
|
+
|
|
897
|
+
/** Text-to-speech providers */
|
|
898
|
+
export const TTS_PROVIDERS = [
|
|
899
|
+
"elevenlabs-v3",
|
|
900
|
+
"elevenlabs-turbo",
|
|
901
|
+
"elevenlabs-multilingual",
|
|
902
|
+
"elevenlabs",
|
|
903
|
+
] as const
|
|
904
|
+
export type TtsProvider = typeof TTS_PROVIDERS[number]
|
|
905
|
+
|
|
906
|
+
/** Text-to-audio providers */
|
|
907
|
+
export const TEXT_TO_AUDIO_PROVIDERS = [
|
|
908
|
+
// Replicate disabled
|
|
909
|
+
// "tangoflux",
|
|
910
|
+
"elevenlabs-sfx",
|
|
911
|
+
] as const
|
|
912
|
+
export type TextToAudioProvider = typeof TEXT_TO_AUDIO_PROVIDERS[number]
|
|
913
|
+
|
|
914
|
+
/** Music generation providers */
|
|
915
|
+
export const MUSIC_PROVIDERS = [
|
|
916
|
+
// Replicate disabled
|
|
917
|
+
// "musicgen",
|
|
918
|
+
"minimax",
|
|
919
|
+
// TODO: surface Suno V5 here for discoverability — needs backend wiring
|
|
920
|
+
// through suno-client.ts (Suno uses /api/v1/generate, NOT the standard
|
|
921
|
+
// /api/v1/jobs/createTask path runKieTask uses). Tracked as a separate
|
|
922
|
+
// follow-up PR. Until then, Suno is reachable via the standalone
|
|
923
|
+
// `suno-generate` node.
|
|
924
|
+
// Replicate disabled
|
|
925
|
+
// "lyria",
|
|
926
|
+
// "bark",
|
|
927
|
+
] as const
|
|
928
|
+
export type MusicProvider = typeof MUSIC_PROVIDERS[number]
|
|
929
|
+
|
|
930
|
+
/** Transcription providers */
|
|
931
|
+
export const TRANSCRIBE_PROVIDERS = [
|
|
932
|
+
// Replicate disabled
|
|
933
|
+
// "whisper",
|
|
934
|
+
// "incredibly-fast-whisper",
|
|
935
|
+
"elevenlabs-stt",
|
|
936
|
+
] as const
|
|
937
|
+
export type TranscribeProvider = typeof TRANSCRIBE_PROVIDERS[number]
|
|
938
|
+
|
|
939
|
+
/** Script generation providers */
|
|
940
|
+
export const SCRIPT_PROVIDERS = [
|
|
941
|
+
"gemini",
|
|
942
|
+
"claude",
|
|
943
|
+
"gpt",
|
|
944
|
+
] as const
|
|
945
|
+
export type ScriptProvider = typeof SCRIPT_PROVIDERS[number]
|
|
946
|
+
|
|
947
|
+
/** AI writer providers */
|
|
948
|
+
export const AI_WRITER_PROVIDERS = [
|
|
949
|
+
"claude",
|
|
950
|
+
] as const
|
|
951
|
+
export type AiWriterProvider = typeof AI_WRITER_PROVIDERS[number]
|
|
952
|
+
|
|
953
|
+
/** QA check providers */
|
|
954
|
+
export const QA_CHECK_PROVIDERS = [
|
|
955
|
+
"claude",
|
|
956
|
+
"gpt",
|
|
957
|
+
] as const
|
|
958
|
+
export type QaCheckProvider = typeof QA_CHECK_PROVIDERS[number]
|
|
959
|
+
|
|
960
|
+
/** Suno model versions */
|
|
961
|
+
export const SUNO_MODELS = [
|
|
962
|
+
"V4",
|
|
963
|
+
"V4_5",
|
|
964
|
+
"V4_5PLUS",
|
|
965
|
+
"V4_5ALL",
|
|
966
|
+
"V5",
|
|
967
|
+
"V5_5",
|
|
968
|
+
] as const
|
|
969
|
+
export type SunoModel = typeof SUNO_MODELS[number]
|
|
970
|
+
|
|
971
|
+
/** Suno models that support add-instrumental / add-vocals operations */
|
|
972
|
+
export const SUNO_ADD_TRACK_MODELS = ["V4_5PLUS", "V5", "V5_5"] as const
|
|
973
|
+
export type SunoAddTrackModel = typeof SUNO_ADD_TRACK_MODELS[number]
|
|
974
|
+
|
|
975
|
+
/** Voice design models */
|
|
976
|
+
export const VOICE_DESIGN_MODELS = [
|
|
977
|
+
"eleven_ttv_v3",
|
|
978
|
+
"eleven_multilingual_ttv_v2",
|
|
979
|
+
] as const
|
|
980
|
+
export type VoiceDesignModel = typeof VOICE_DESIGN_MODELS[number]
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* ElevenLabs' newest text-to-voice model (all languages) — the default when
|
|
984
|
+
* a voice-design caller omits `model`. Single-sourced here so the route
|
|
985
|
+
* (`backend/src/routes/voice-design.ts`) and the direct-API provider
|
|
986
|
+
* (`backend/src/providers/elevenlabs/voice-design.ts`) can't drift.
|
|
987
|
+
*/
|
|
988
|
+
export const DEFAULT_VOICE_DESIGN_MODEL: VoiceDesignModel = "eleven_ttv_v3"
|
|
989
|
+
|
|
990
|
+
/** I2I providers that support mask-based inpainting */
|
|
991
|
+
export const I2I_MASK_SUPPORT = new Set(["ideogram-edit"])
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* Mask edit tier per image-gen provider (single source of truth for inpaint).
|
|
995
|
+
* - "native" → provider takes a real mask param (reserved for Phase 1.5; none today)
|
|
996
|
+
* - "prompt" → strong instruction editor; inject a region descriptor into the prompt
|
|
997
|
+
* - "composite" → rely on the always-on server-side composite floor only
|
|
998
|
+
* Floor (B·(1−M)+E·M) applies to EVERY tier, so any provider is correct.
|
|
999
|
+
*/
|
|
1000
|
+
export type ImageMaskMode = "native" | "prompt" | "composite"
|
|
1001
|
+
|
|
1002
|
+
export const IMAGE_MASK_MODE: Record<ImageGenProvider, ImageMaskMode> = {
|
|
1003
|
+
"nano-banana": "prompt",
|
|
1004
|
+
"nano-banana-pro": "prompt",
|
|
1005
|
+
"nano-banana-2": "prompt",
|
|
1006
|
+
"gpt-image": "prompt",
|
|
1007
|
+
"gpt-image-2": "prompt",
|
|
1008
|
+
"seedream": "prompt",
|
|
1009
|
+
"seedream-5-lite": "prompt",
|
|
1010
|
+
"qwen": "prompt",
|
|
1011
|
+
"flux-kontext": "prompt",
|
|
1012
|
+
"flux-kontext-max": "prompt",
|
|
1013
|
+
"flux": "composite",
|
|
1014
|
+
"flux-flex": "composite",
|
|
1015
|
+
"grok": "composite",
|
|
1016
|
+
"imagen4": "composite",
|
|
1017
|
+
"imagen4-fast": "composite",
|
|
1018
|
+
"imagen4-ultra": "composite",
|
|
1019
|
+
"ideogram-v3": "composite",
|
|
1020
|
+
"z-image": "composite",
|
|
1021
|
+
"wan-2.7": "composite",
|
|
1022
|
+
"wan-2.7-pro": "composite",
|
|
1023
|
+
"flux-2-klein": "composite",
|
|
1024
|
+
"flux-2-pro": "composite",
|
|
1025
|
+
"flux-2-max": "composite",
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/** I2I providers that support a strength/denoising parameter */
|
|
1029
|
+
export const I2I_STRENGTH_SUPPORT: Record<string, { min: number; max: number; step: number; default: number }> = {
|
|
1030
|
+
"ideogram-remix": { min: 0.01, max: 1, step: 0.01, default: 0.8 },
|
|
1031
|
+
"qwen-i2i": { min: 0, max: 1, step: 0.01, default: 0.8 },
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/** Models that accept a seed parameter for reproducible generation */
|
|
1035
|
+
export const SEED_SUPPORT = new Set([
|
|
1036
|
+
"ideogram-remix", "ideogram-reframe", "ideogram-v3",
|
|
1037
|
+
"qwen", "qwen-i2i", "qwen-edit",
|
|
1038
|
+
"flux", "flux-flex", "flux-i2i", "flux-pro-i2i", "flux-kontext", "flux-kontext-max",
|
|
1039
|
+
"flux-2-klein", "kontext-multi",
|
|
1040
|
+
])
|
|
1041
|
+
|
|
1042
|
+
/** Ideogram models that support rendering_speed selection (TURBO/BALANCED/QUALITY) */
|
|
1043
|
+
export const RENDERING_SPEED_SUPPORT = new Set([
|
|
1044
|
+
"ideogram-remix", "ideogram-reframe", "ideogram-v3",
|
|
1045
|
+
])
|
|
1046
|
+
|
|
1047
|
+
/** Models that accept guidance_scale for controlling prompt adherence */
|
|
1048
|
+
export const GUIDANCE_SCALE_SUPPORT: Record<string, { min: number; max: number; step: number; default: number }> = {
|
|
1049
|
+
"qwen-i2i": { min: 1, max: 20, step: 0.5, default: 7 },
|
|
1050
|
+
"qwen-edit": { min: 1, max: 20, step: 0.5, default: 7 },
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// =====================================================================
|
|
1054
|
+
// Video variable pricing — duration-based and audio-addon pricing
|
|
1055
|
+
// =====================================================================
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* Video models where credit cost varies by duration.
|
|
1059
|
+
* Maps provider key → duration tier breakpoints.
|
|
1060
|
+
* Values are verified by the pricing verification script (backend/scripts/verify-kie-pricing.ts).
|
|
1061
|
+
* TODO: Run verification script and update costs after confirming actual KIE pricing.
|
|
1062
|
+
*/
|
|
1063
|
+
export const DURATION_PRICED_PROVIDERS = new Set([
|
|
1064
|
+
"seedance-2-extend",
|
|
1065
|
+
"kling-3.0",
|
|
1066
|
+
"kling-3-omni",
|
|
1067
|
+
"kling",
|
|
1068
|
+
"kling-turbo",
|
|
1069
|
+
"kling-master",
|
|
1070
|
+
"grok-i2v",
|
|
1071
|
+
"wan-i2v",
|
|
1072
|
+
"hailuo-2.3-pro",
|
|
1073
|
+
"hailuo-2.3",
|
|
1074
|
+
"hailuo-standard",
|
|
1075
|
+
"seedance",
|
|
1076
|
+
"seedance-2",
|
|
1077
|
+
"seedance-2-fast",
|
|
1078
|
+
"seedance-2-mini",
|
|
1079
|
+
"grok-imagine-video-1.5",
|
|
1080
|
+
])
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* Seedance 2.0 family — shared across UI gating, payload building, and pricing.
|
|
1084
|
+
* Expanded whenever a new Seedance 2.x variant ships.
|
|
1085
|
+
*/
|
|
1086
|
+
export const SEEDANCE_2_PROVIDERS = new Set<string>([
|
|
1087
|
+
"seedance-2",
|
|
1088
|
+
"seedance-2-fast",
|
|
1089
|
+
"seedance-2-mini",
|
|
1090
|
+
])
|
|
1091
|
+
|
|
1092
|
+
export function isSeedance2Provider(provider: string | undefined): boolean {
|
|
1093
|
+
return !!provider && SEEDANCE_2_PROVIDERS.has(provider)
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* Default aspect ratio for a video provider when the node carries no explicit
|
|
1098
|
+
* `aspectRatio`. Seedance 2.x defaults to `"adaptive"` (output matches the
|
|
1099
|
+
* wired input — fits the unified-inputs model); every other provider keeps the
|
|
1100
|
+
* historical `"16:9"`. Single source of truth so every run-default / display-
|
|
1101
|
+
* fallback site stays in lock-step (preview = run). Applies to ALL Seedance
|
|
1102
|
+
* modes incl. text-to-video — KIE accepts `adaptive` there too.
|
|
1103
|
+
*/
|
|
1104
|
+
export function defaultVideoAspectRatio(provider: string | undefined): string {
|
|
1105
|
+
return isSeedance2Provider(provider) ? "adaptive" : "16:9"
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* Google VEO family (Quality / Fast / Lite). VEO goes through its own KIE
|
|
1110
|
+
* endpoint and image-handling path, so callers branch on this in several places.
|
|
1111
|
+
*/
|
|
1112
|
+
export const VEO_PROVIDERS = new Set<string>([
|
|
1113
|
+
"veo3",
|
|
1114
|
+
"veo3.1",
|
|
1115
|
+
"veo3_lite",
|
|
1116
|
+
])
|
|
1117
|
+
|
|
1118
|
+
export function isVeoProvider(provider: string | undefined): boolean {
|
|
1119
|
+
return !!provider && VEO_PROVIDERS.has(provider)
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
/** KIE.ai limits for Seedance 2.0 multimodal reference arrays. */
|
|
1123
|
+
export const SEEDANCE_2_REF_LIMITS = {
|
|
1124
|
+
images: 9,
|
|
1125
|
+
videos: 3,
|
|
1126
|
+
audio: 3,
|
|
1127
|
+
} as const
|
|
1128
|
+
|
|
1129
|
+
/**
|
|
1130
|
+
* Trim-stitch parameters for the seedance-2-extend provider (spike-validated
|
|
1131
|
+
* 2026-06-11):
|
|
1132
|
+
* the model's extension output wobbles for its first ~3 frames and the source
|
|
1133
|
+
* tail's last ~4 frames lead into it; dropping both and butt-joining yields a
|
|
1134
|
+
* seam SMOOTHER than ordinary in-clip motion (PSNR 34.4dB vs 30.6dB
|
|
1135
|
+
* adjacent-frame baseline). The 0.15s timeline-anchored audio fades kill
|
|
1136
|
+
* boundary clicks without shifting sync (combineVideos cut+crossfade path).
|
|
1137
|
+
*/
|
|
1138
|
+
export const SEEDANCE_2_EXTEND_STITCH = {
|
|
1139
|
+
/** Frames dropped from the END of the source clip. */
|
|
1140
|
+
trimTailFrames: 4,
|
|
1141
|
+
/** Frames dropped from the START of the generated extension. */
|
|
1142
|
+
trimHeadFrames: 3,
|
|
1143
|
+
/** Boundary audio fade length (seconds), timeline-preserving. */
|
|
1144
|
+
audioFadeSec: 0.15,
|
|
1145
|
+
} as const
|
|
1146
|
+
|
|
1147
|
+
/**
|
|
1148
|
+
* Per-provider max reference-audio duration (seconds) for Seedance 2.0 r2v
|
|
1149
|
+
* (audio-driven image-to-video). KIE rejects longer clips with a 400 BEFORE
|
|
1150
|
+
* generation (e.g. "audio duration must be ≤ 15.2 for dreamina-seedance-2-0-fast
|
|
1151
|
+
* in r2v"). Data-driven: only providers with a VERIFIED limit are listed, so an
|
|
1152
|
+
* unknown provider is never false-rejected.
|
|
1153
|
+
* - seedance-2-fast: 15.2s (verified against KIE).
|
|
1154
|
+
* - seedance-2 (non-fast): limit unverified → not enforced yet (add after
|
|
1155
|
+
* confirming via docs.kie.ai).
|
|
1156
|
+
*/
|
|
1157
|
+
export const SEEDANCE_2_R2V_MAX_AUDIO_SEC_BY_PROVIDER: Record<string, number> = {
|
|
1158
|
+
"seedance-2-fast": 15.2,
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/** The verified r2v reference-audio cap (seconds) for a provider, or null when
|
|
1162
|
+
* none is enforced. */
|
|
1163
|
+
export function seedance2AudioLimitSec(provider: string | undefined): number | null {
|
|
1164
|
+
if (!provider) return null
|
|
1165
|
+
return SEEDANCE_2_R2V_MAX_AUDIO_SEC_BY_PROVIDER[provider] ?? null
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/**
|
|
1169
|
+
* First reference-audio duration (seconds) that exceeds the provider's r2v cap,
|
|
1170
|
+
* or null when all are within limit (or the provider has no enforced cap).
|
|
1171
|
+
* Non-finite durations (probe failures) are ignored. Used to reject over-long
|
|
1172
|
+
* audio BEFORE submitting to the provider — which would otherwise 400.
|
|
1173
|
+
*/
|
|
1174
|
+
export function findSeedance2AudioOverLimit(
|
|
1175
|
+
provider: string | undefined,
|
|
1176
|
+
durationsSec: readonly number[],
|
|
1177
|
+
): number | null {
|
|
1178
|
+
const limit = seedance2AudioLimitSec(provider)
|
|
1179
|
+
if (limit === null) return null
|
|
1180
|
+
for (const d of durationsSec) {
|
|
1181
|
+
if (Number.isFinite(d) && d > limit) return d
|
|
1182
|
+
}
|
|
1183
|
+
return null
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* Per-provider connection caps for the typed reference handles on Generate Video
|
|
1188
|
+
* AND the cap for the server-side `connectedReferences` assembly (`imageCap`).
|
|
1189
|
+
*
|
|
1190
|
+
* The KEY SET is bound 1:1 to MODEL_CATALOG (kind:"video" + features:
|
|
1191
|
+
* ["reference-image"]) by the drift guard in `__tests__/video-ref-limits.test.ts`:
|
|
1192
|
+
* adding a ref-capable video model to the catalog without a cap here — or a cap
|
|
1193
|
+
* here without the catalog flag — fails CI. So the CATALOG is the single source
|
|
1194
|
+
* for "is this model ref-capable", and this map is the single source for the
|
|
1195
|
+
* NUMERIC caps (sourced from the provider configs, see per-line notes).
|
|
1196
|
+
* Providers absent from the map = 0 caps (the popover dims the handle with
|
|
1197
|
+
* "Not supported by [Model]"; the API assembly strips `{image:N}` tokens).
|
|
1198
|
+
*/
|
|
1199
|
+
export const VIDEO_REF_LIMITS_BY_PROVIDER: Record<
|
|
1200
|
+
string,
|
|
1201
|
+
{ images?: number; videos?: number; audio?: number } | undefined
|
|
1202
|
+
> = {
|
|
1203
|
+
// Seedance 2 — full multimodal caps.
|
|
1204
|
+
"seedance-2": { ...SEEDANCE_2_REF_LIMITS },
|
|
1205
|
+
"seedance-2-fast": { ...SEEDANCE_2_REF_LIMITS },
|
|
1206
|
+
"seedance-2-mini": { ...SEEDANCE_2_REF_LIMITS },
|
|
1207
|
+
// Multi-image reference providers.
|
|
1208
|
+
"gemini-omni-video": { images: 7, videos: 1 },
|
|
1209
|
+
"kling-3-omni": { images: 7 }, // catalog/docs: "end frame + up to 7 reference images"
|
|
1210
|
+
"grok-i2v": { images: 7 }, // backend kie/models.ts maxRefImages: 7
|
|
1211
|
+
"happyhorse-ref2v": { images: 9 }, // backend kie/models.ts maxRefImages: 9
|
|
1212
|
+
// VEO 3.x — REFERENCE_2_VIDEO path caps refs at 3 (kie/video.ts slice(0, 3)).
|
|
1213
|
+
"veo3": { images: 3 },
|
|
1214
|
+
"veo3.1": { images: 3 },
|
|
1215
|
+
"veo3_lite": { images: 3 },
|
|
1216
|
+
// NOTE: wan-i2v / hailuo-2.3[-pro] / bytedance-pro[-fast] / grok-imagine-video-1.5
|
|
1217
|
+
// are deliberately ABSENT. The 2026-06-28 audit (video-reference-features.test.ts)
|
|
1218
|
+
// verified backend reference-forwarding paths and these i2v models have none —
|
|
1219
|
+
// a single image_url start frame only. Advertising a ref cap would silently drop
|
|
1220
|
+
// the user's references (the grok-imagine-video-1.5 bug). Add only with a
|
|
1221
|
+
// verified provider path + the catalog `reference-image` feature.
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
/**
|
|
1225
|
+
* Video models where credit cost depends on resolution AND whether a video
|
|
1226
|
+
* reference is connected. Identifier suffix: `:{resolution}[-ref]`.
|
|
1227
|
+
* Seedance 2.0 family uses per-second billing split 480p/720p × with-ref/no-ref.
|
|
1228
|
+
*/
|
|
1229
|
+
export const RESOLUTION_VIDEO_REF_PRICING = SEEDANCE_2_PROVIDERS
|
|
1230
|
+
|
|
1231
|
+
/**
|
|
1232
|
+
* Video models priced by (duration × resolution) WITHOUT a video-ref dimension.
|
|
1233
|
+
* Identifier suffix: `:{resolution}`, appended after the duration tier; value =
|
|
1234
|
+
* the provider's PRICED resolution tiers. Any resolution outside the list
|
|
1235
|
+
* (or undefined) collapses to the FIRST entry — the provider's default tier —
|
|
1236
|
+
* so the builder can never emit an unpriced composite (the hard-fail guard
|
|
1237
|
+
* fuzzes the full resolution space). Distinct from
|
|
1238
|
+
* RESOLUTION_VIDEO_REF_PRICING (Seedance gen), which also adds `-ref` variants.
|
|
1239
|
+
*/
|
|
1240
|
+
export const RESOLUTION_DURATION_PRICING: Record<string, readonly string[]> = {
|
|
1241
|
+
// KIE supports only 480p/720p here; 480p is the default.
|
|
1242
|
+
"grok-imagine-video-1.5": ["480p", "720p"],
|
|
1243
|
+
// seedance-2-extend always uses a video ref, so pricing has no -ref
|
|
1244
|
+
// dimension — duration tier + ":res" only (rates = seedance-2 -ref + stitch).
|
|
1245
|
+
"seedance-2-extend": ["480p", "720p", "1080p"],
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
/**
|
|
1249
|
+
* Video generation models whose provider API natively accepts a "match the
|
|
1250
|
+
* visual input's aspect" token, mapped to that provider's EXACT token.
|
|
1251
|
+
* Consumed by the backend's `resolveSourceMatchedAspect`: members get the
|
|
1252
|
+
* native token (no probe round-trip, exact match even for off-catalog ratios
|
|
1253
|
+
* like 4:5); everyone else falls back to ffprobe + closest catalog ratio.
|
|
1254
|
+
*
|
|
1255
|
+
* Only add entries whose match-the-input semantics are verified against the
|
|
1256
|
+
* provider's docs AND a live job — e.g. grok-imagine-video-1.5 accepts
|
|
1257
|
+
* "auto", but that means "provider default", NOT "match input", so it does
|
|
1258
|
+
* not belong here.
|
|
1259
|
+
*
|
|
1260
|
+
* seedance-2 family: `adaptive` per docs.kie.ai/market/bytedance/seedance-2
|
|
1261
|
+
* + seedance-2-fast (enum'd alongside the fixed ratios; default 16:9).
|
|
1262
|
+
*/
|
|
1263
|
+
export const NATIVE_ADAPTIVE_ASPECT: Record<string, string> = {
|
|
1264
|
+
"seedance-2": "adaptive",
|
|
1265
|
+
"seedance-2-fast": "adaptive",
|
|
1266
|
+
"seedance-2-mini": "adaptive",
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
/**
|
|
1270
|
+
* Video providers that REQUIRE an input image (image-to-video only) even though
|
|
1271
|
+
* they're listed in TEXT_TO_VIDEO_PROVIDERS for unified-node visibility. The
|
|
1272
|
+
* `/v1/text-to-video` route early-returns a clean 400 for these before any job
|
|
1273
|
+
* is created, instead of letting the prompt-only request fail at the provider.
|
|
1274
|
+
*
|
|
1275
|
+
* DERIVED from MODEL_CATALOG (the capability source of truth): every video
|
|
1276
|
+
* model whose `modes` lacks "t2v" and that isn't remapped to a t2v twin by
|
|
1277
|
+
* VIDEO_MODE_ALIASES. Hand-listing this set is what let kling-3-omni,
|
|
1278
|
+
* kling-master, hailuo-2.3(-pro), bytedance-pro-fast and happyhorse-ref2v
|
|
1279
|
+
* crash the t2v route with a raw Zod enum error — new i2v-only models now get
|
|
1280
|
+
* the friendly gate automatically when their catalog entry is added.
|
|
1281
|
+
*/
|
|
1282
|
+
export const VIDEO_PROVIDERS_REQUIRING_IMAGE: ReadonlySet<string> = new Set(
|
|
1283
|
+
Object.values(MODEL_CATALOG)
|
|
1284
|
+
.filter((m) =>
|
|
1285
|
+
m.kind === "video" &&
|
|
1286
|
+
m.modes.includes("i2v") &&
|
|
1287
|
+
!m.modes.includes("t2v") &&
|
|
1288
|
+
(VIDEO_GEN_PROVIDERS as readonly string[]).includes(m.id) &&
|
|
1289
|
+
resolveVideoProviderForMode(m.id, "text-to-video") === m.id,
|
|
1290
|
+
)
|
|
1291
|
+
.map((m) => m.id),
|
|
1292
|
+
)
|
|
1293
|
+
|
|
1294
|
+
/** True when a video provider can only run image-to-video (image required). */
|
|
1295
|
+
export function videoProviderRequiresImage(provider: string | undefined): boolean {
|
|
1296
|
+
return !!provider && VIDEO_PROVIDERS_REQUIRING_IMAGE.has(provider)
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
/**
|
|
1300
|
+
* Video models where enabling audio/sound incurs an additional cost.
|
|
1301
|
+
* The audio addon is expressed as a separate composite identifier suffix.
|
|
1302
|
+
*/
|
|
1303
|
+
export const AUDIO_ADDON_PROVIDERS = new Set([
|
|
1304
|
+
"kling-3.0",
|
|
1305
|
+
"kling",
|
|
1306
|
+
])
|
|
1307
|
+
|
|
1308
|
+
/**
|
|
1309
|
+
* How a video model handles an audio track. Single source of truth for
|
|
1310
|
+
* audio/speech capability across the whole stack — the config-panel audio
|
|
1311
|
+
* toggle (show / lock / disable + warn), the Story→Video dialogue auto-pick
|
|
1312
|
+
* (revoice vs TTS + lip-sync), provider option wiring (`sound` vs
|
|
1313
|
+
* `generateAudio`), and the audio credit suffix.
|
|
1314
|
+
*
|
|
1315
|
+
* Consolidates signals that were previously scattered + inconsistent:
|
|
1316
|
+
* - `KIE_VIDEO_MODELS` / `KIE_TEXT_TO_VIDEO_MODELS` `extraParams.sound` (Kling)
|
|
1317
|
+
* - `…extraParams.generate_audio` (Seedance)
|
|
1318
|
+
* - VEO native audio (implicit, always on)
|
|
1319
|
+
* - `AUDIO_ADDON_PROVIDERS` (cost) + `SEEDANCE_2_PROVIDERS` (reference audio)
|
|
1320
|
+
*
|
|
1321
|
+
* "none" — silent model; no audio output. UI disables the audio
|
|
1322
|
+
* toggle with an explanatory note; the pipeline uses TTS +
|
|
1323
|
+
* lip-sync for any dialogue.
|
|
1324
|
+
* "ambient" — generates ambient sound / SFX matched to the scene, NOT
|
|
1325
|
+
* lip-synced spoken dialogue. Toggle is offered; the
|
|
1326
|
+
* pipeline still uses TTS + lip-sync for dialogue.
|
|
1327
|
+
* "native_speech" — bakes spoken dialogue + lip movement from the prompt
|
|
1328
|
+
* (VEO 3.x). The pipeline injects the dialogue line, enables
|
|
1329
|
+
* audio, and revoices the clip to the character's saved voice.
|
|
1330
|
+
* "audio_driven" — lip-syncs to a supplied reference-audio track (Seedance
|
|
1331
|
+
* 2.0 multimodal). The pipeline synthesises the character's
|
|
1332
|
+
* voice first, feeds it as reference audio, and skips the
|
|
1333
|
+
* separate lip-sync pass.
|
|
1334
|
+
*/
|
|
1335
|
+
export type VideoAudioMode = "none" | "ambient" | "native_speech" | "audio_driven"
|
|
1336
|
+
|
|
1337
|
+
export interface VideoAudioCapability {
|
|
1338
|
+
mode: VideoAudioMode
|
|
1339
|
+
/** Provider-option field carrying the on/off toggle, when user-controllable. */
|
|
1340
|
+
field?: "generateAudio" | "sound"
|
|
1341
|
+
/** Audio is always produced and can't be turned off by the user (VEO 3.x). */
|
|
1342
|
+
alwaysOn?: boolean
|
|
1343
|
+
/** Enabling audio raises the credit cost (Kling — see AUDIO_ADDON_PROVIDERS). */
|
|
1344
|
+
affectsCost?: boolean
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
/**
|
|
1348
|
+
* Per-model audio capability. Only models that produce SOME audio are listed;
|
|
1349
|
+
* anything absent defaults to `{ mode: "none" }` via `getVideoAudioCapability`,
|
|
1350
|
+
* so silent models (minimax, hailuo, wan, grok-i2v, gemini-omni-video, runway,
|
|
1351
|
+
* pika, …) need no entry. New audio-capable models MUST be added here — the
|
|
1352
|
+
* `video-audio-capability` guard test cross-checks this map against the model
|
|
1353
|
+
* configs' `extraParams.sound` / `generate_audio` + VEO/Seedance-2 sets so a
|
|
1354
|
+
* forgotten entry fails CI rather than silently disabling audio.
|
|
1355
|
+
*/
|
|
1356
|
+
export const VIDEO_AUDIO_CAPABILITY: Record<string, VideoAudioCapability> = {
|
|
1357
|
+
// VEO 3.x — native spoken dialogue + lip movement; always on (no toggle).
|
|
1358
|
+
veo3: { mode: "native_speech", alwaysOn: true },
|
|
1359
|
+
"veo3.1": { mode: "native_speech", alwaysOn: true },
|
|
1360
|
+
veo3_lite: { mode: "native_speech", alwaysOn: true },
|
|
1361
|
+
// Kling 2.6 / 3.0 — ambient sound/SFX toggle; not lip-synced speech. Cost-affecting.
|
|
1362
|
+
kling: { mode: "ambient", field: "sound", affectsCost: true },
|
|
1363
|
+
"kling-3.0": { mode: "ambient", field: "sound", affectsCost: true },
|
|
1364
|
+
// Seedance 1.x — optional ambient audio (generate_audio); not dialogue.
|
|
1365
|
+
seedance: { mode: "ambient", field: "generateAudio" },
|
|
1366
|
+
// Seedance 2.0 — multimodal; lip-syncs to a supplied reference-audio track.
|
|
1367
|
+
"seedance-2": { mode: "audio_driven", field: "generateAudio" },
|
|
1368
|
+
"seedance-2-fast": { mode: "audio_driven", field: "generateAudio" },
|
|
1369
|
+
"seedance-2-mini": { mode: "audio_driven", field: "generateAudio" },
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
const VIDEO_AUDIO_NONE: VideoAudioCapability = { mode: "none" }
|
|
1373
|
+
|
|
1374
|
+
/** Audio capability for a video model. Unlisted/undefined → silent (`none`). */
|
|
1375
|
+
export function getVideoAudioCapability(
|
|
1376
|
+
model: string | undefined,
|
|
1377
|
+
): VideoAudioCapability {
|
|
1378
|
+
return (model ? VIDEO_AUDIO_CAPABILITY[model] : undefined) ?? VIDEO_AUDIO_NONE
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
/**
|
|
1382
|
+
* True when the model produces any audio track at all. Drives the config-panel
|
|
1383
|
+
* audio toggle: `false` → disable the toggle + show a "this model has no audio"
|
|
1384
|
+
* note (never restrict the model choice — just explain the limitation).
|
|
1385
|
+
*/
|
|
1386
|
+
export function videoModelSupportsAudio(model: string | undefined): boolean {
|
|
1387
|
+
return getVideoAudioCapability(model).mode !== "none"
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
/**
|
|
1391
|
+
* True when the model can produce lip-synced spoken DIALOGUE — either natively
|
|
1392
|
+
* (VEO) or driven by a supplied audio track (Seedance 2.0). Drives the Story→Video
|
|
1393
|
+
* dialogue auto-pick: in-model speech + character revoice (VEO) / character-voiced
|
|
1394
|
+
* reference audio (Seedance 2.0) vs. the TTS + separate-lip-sync fallback.
|
|
1395
|
+
* Ambient-only models (Kling, Seedance 1.x) return `false` — their audio is SFX,
|
|
1396
|
+
* not speech.
|
|
1397
|
+
*/
|
|
1398
|
+
export function videoModelCanSpeakDialogue(model: string | undefined): boolean {
|
|
1399
|
+
const mode = getVideoAudioCapability(model).mode
|
|
1400
|
+
return mode === "native_speech" || mode === "audio_driven"
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
/**
|
|
1404
|
+
* Write the caller's neutral "enable native audio" intent onto a KIE provider
|
|
1405
|
+
* `input` under whichever field the chosen model actually reads, per
|
|
1406
|
+
* `VIDEO_AUDIO_CAPABILITY.field`. This is the single dispatch point that fixes
|
|
1407
|
+
* the recurring "sound toggle dropped on Seedance/VEO" bug class: callers pass
|
|
1408
|
+
* the model-agnostic toggle ONCE and never need to know that Kling's lever is
|
|
1409
|
+
* `sound` while Seedance's is `generate_audio`.
|
|
1410
|
+
*
|
|
1411
|
+
* - `sound` is the canonical neutral name; `generateAudio` is accepted as a
|
|
1412
|
+
* legacy alias (the workflow node + some clients send that key). `sound` wins
|
|
1413
|
+
* if both are present.
|
|
1414
|
+
* - **Cost-affecting models (Kling) honour ONLY the canonical `sound` lever** —
|
|
1415
|
+
* the same field the `:audio` credit surcharge keys off (`AUDIO_ADDON_PROVIDERS`
|
|
1416
|
+
* in `credit-identifiers.ts`). This makes a billed-but-not-generated (or
|
|
1417
|
+
* generated-but-not-billed) divergence structurally impossible: the model and
|
|
1418
|
+
* the surcharge read the same flag. Free models accept the `generateAudio`
|
|
1419
|
+
* alias too, where mis-billing isn't possible.
|
|
1420
|
+
* - No-op for `alwaysOn` models (VEO — audio can't be toggled) and silent /
|
|
1421
|
+
* unlisted models (no `field`), and when the caller expressed no intent
|
|
1422
|
+
* (leaves the model's own default in place).
|
|
1423
|
+
*
|
|
1424
|
+
* Future audio models are covered automatically by adding a
|
|
1425
|
+
* `VIDEO_AUDIO_CAPABILITY` entry — already guarded by the
|
|
1426
|
+
* `video-audio-capability` drift test, so this never silently regresses.
|
|
1427
|
+
*/
|
|
1428
|
+
export function applyVideoAudioToggle(
|
|
1429
|
+
input: Record<string, unknown>,
|
|
1430
|
+
provider: string | undefined,
|
|
1431
|
+
opts: { sound?: boolean; generateAudio?: boolean } | undefined,
|
|
1432
|
+
): void {
|
|
1433
|
+
const cap = getVideoAudioCapability(provider)
|
|
1434
|
+
if (!cap.field) return // alwaysOn (VEO) or silent/unlisted model — nothing to toggle
|
|
1435
|
+
// Cost-affecting models stay on the canonical `sound` lever so the model and
|
|
1436
|
+
// the credit surcharge can never read different flags; free models also accept
|
|
1437
|
+
// the `generateAudio` alias. `sound` is canonical and wins when both are set.
|
|
1438
|
+
const enabled = cap.affectsCost ? opts?.sound : (opts?.sound ?? opts?.generateAudio)
|
|
1439
|
+
if (enabled === undefined) return // no intent — keep the model's own default
|
|
1440
|
+
if (cap.field === "generateAudio") input.generate_audio = enabled
|
|
1441
|
+
else input.sound = enabled
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
/**
|
|
1445
|
+
* Video models where a quality/mode parameter (e.g., videoSize "high") incurs higher cost.
|
|
1446
|
+
* When provider is in this set and mode is "high", ":high" is appended to the identifier.
|
|
1447
|
+
*/
|
|
1448
|
+
export const MODE_ADDON_PROVIDERS = new Set<string>([
|
|
1449
|
+
])
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* VEO 3.x providers where credit cost depends on the requested output
|
|
1453
|
+
* resolution (720p default vs 1080p inline; 4K is via the separate
|
|
1454
|
+
* upgrade endpoint and isn't part of this set). When provider is in
|
|
1455
|
+
* this set and resolution !== "720p", `:1080p` is appended to the
|
|
1456
|
+
* identifier so the credit lookup hits the per-resolution rate.
|
|
1457
|
+
*
|
|
1458
|
+
* Per KIE pricing (verified 2026-05-06):
|
|
1459
|
+
* veo3.1 (Fast): 720p=60 KIE cr, 1080p=65 KIE cr
|
|
1460
|
+
* veo3_lite: 720p=30 KIE cr, 1080p=35 KIE cr
|
|
1461
|
+
* veo3 (Quality) is not in this set yet — pricing not in our reference data.
|
|
1462
|
+
*/
|
|
1463
|
+
export const VEO_RESOLUTION_TIERED_PROVIDERS = new Set<string>([
|
|
1464
|
+
"veo3.1",
|
|
1465
|
+
"veo3_lite",
|
|
1466
|
+
])
|
|
1467
|
+
|
|
1468
|
+
/**
|
|
1469
|
+
* Video variable pricing config — which params affect credit cost per model.
|
|
1470
|
+
* "duration" = cost varies by video length
|
|
1471
|
+
* "duration+audio" = cost varies by length AND audio on/off
|
|
1472
|
+
*/
|
|
1473
|
+
export const VIDEO_VARIABLE_PRICING: Record<string, "duration" | "duration+audio" | "duration+mode" | "duration+resolution+ref" | "duration+resolution"> = {
|
|
1474
|
+
"kling-3.0": "duration+audio",
|
|
1475
|
+
"kling-3-omni": "duration",
|
|
1476
|
+
"kling": "duration+audio",
|
|
1477
|
+
"kling-turbo": "duration",
|
|
1478
|
+
"kling-master": "duration",
|
|
1479
|
+
"grok-i2v": "duration",
|
|
1480
|
+
"wan-i2v": "duration",
|
|
1481
|
+
"hailuo-2.3-pro": "duration",
|
|
1482
|
+
"hailuo-2.3": "duration",
|
|
1483
|
+
"hailuo-standard": "duration",
|
|
1484
|
+
"seedance": "duration",
|
|
1485
|
+
"seedance-2": "duration+resolution+ref",
|
|
1486
|
+
"seedance-2-fast": "duration+resolution+ref",
|
|
1487
|
+
"seedance-2-mini": "duration+resolution+ref",
|
|
1488
|
+
// Grok Imagine Video 1.5 — per-second billing split 480p/720p (no video-ref dimension).
|
|
1489
|
+
"grok-imagine-video-1.5": "duration+resolution",
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
/**
|
|
1493
|
+
* Duration tier breakpoints for variable-priced video models.
|
|
1494
|
+
* Maps provider → array of { maxSeconds, suffix } in ascending order.
|
|
1495
|
+
* The first tier whose maxSeconds >= requested duration is used.
|
|
1496
|
+
*/
|
|
1497
|
+
export const VIDEO_DURATION_TIERS: Record<string, Array<{ maxSeconds: number; suffix: string }>> = {
|
|
1498
|
+
"kling-3.0": [
|
|
1499
|
+
{ maxSeconds: 5, suffix: "5s" },
|
|
1500
|
+
{ maxSeconds: 10, suffix: "10s" },
|
|
1501
|
+
{ maxSeconds: 15, suffix: "15s" },
|
|
1502
|
+
],
|
|
1503
|
+
"kling-3-omni": [
|
|
1504
|
+
{ maxSeconds: 5, suffix: "5s" },
|
|
1505
|
+
{ maxSeconds: 10, suffix: "10s" },
|
|
1506
|
+
{ maxSeconds: 15, suffix: "15s" },
|
|
1507
|
+
],
|
|
1508
|
+
"kling": [
|
|
1509
|
+
{ maxSeconds: 5, suffix: "5s" },
|
|
1510
|
+
{ maxSeconds: 10, suffix: "10s" },
|
|
1511
|
+
],
|
|
1512
|
+
"kling-turbo": [
|
|
1513
|
+
{ maxSeconds: 5, suffix: "5s" },
|
|
1514
|
+
{ maxSeconds: 10, suffix: "10s" },
|
|
1515
|
+
],
|
|
1516
|
+
"kling-master": [
|
|
1517
|
+
{ maxSeconds: 5, suffix: "5s" },
|
|
1518
|
+
{ maxSeconds: 10, suffix: "10s" },
|
|
1519
|
+
],
|
|
1520
|
+
"grok-i2v": [
|
|
1521
|
+
{ maxSeconds: 6, suffix: "6s" },
|
|
1522
|
+
{ maxSeconds: 10, suffix: "10s" },
|
|
1523
|
+
{ maxSeconds: 15, suffix: "15s" },
|
|
1524
|
+
],
|
|
1525
|
+
"wan-i2v": [
|
|
1526
|
+
{ maxSeconds: 5, suffix: "5s" },
|
|
1527
|
+
{ maxSeconds: 10, suffix: "10s" },
|
|
1528
|
+
{ maxSeconds: 15, suffix: "15s" },
|
|
1529
|
+
],
|
|
1530
|
+
"hailuo-2.3-pro": [
|
|
1531
|
+
{ maxSeconds: 6, suffix: "6s" },
|
|
1532
|
+
{ maxSeconds: 10, suffix: "10s" },
|
|
1533
|
+
],
|
|
1534
|
+
"hailuo-2.3": [
|
|
1535
|
+
{ maxSeconds: 6, suffix: "6s" },
|
|
1536
|
+
{ maxSeconds: 10, suffix: "10s" },
|
|
1537
|
+
],
|
|
1538
|
+
"hailuo-standard": [
|
|
1539
|
+
{ maxSeconds: 6, suffix: "6s" },
|
|
1540
|
+
{ maxSeconds: 10, suffix: "10s" },
|
|
1541
|
+
],
|
|
1542
|
+
"seedance": [
|
|
1543
|
+
{ maxSeconds: 4, suffix: "4s" },
|
|
1544
|
+
{ maxSeconds: 8, suffix: "8s" },
|
|
1545
|
+
{ maxSeconds: 12, suffix: "12s" },
|
|
1546
|
+
],
|
|
1547
|
+
"seedance-2": [
|
|
1548
|
+
{ maxSeconds: 4, suffix: "4s" },
|
|
1549
|
+
{ maxSeconds: 8, suffix: "8s" },
|
|
1550
|
+
{ maxSeconds: 12, suffix: "12s" },
|
|
1551
|
+
{ maxSeconds: 15, suffix: "15s" },
|
|
1552
|
+
],
|
|
1553
|
+
"seedance-2-mini": [
|
|
1554
|
+
{ maxSeconds: 4, suffix: "4s" },
|
|
1555
|
+
{ maxSeconds: 8, suffix: "8s" },
|
|
1556
|
+
{ maxSeconds: 12, suffix: "12s" },
|
|
1557
|
+
{ maxSeconds: 15, suffix: "15s" },
|
|
1558
|
+
],
|
|
1559
|
+
"seedance-2-extend": [
|
|
1560
|
+
{ maxSeconds: 4, suffix: "4s" },
|
|
1561
|
+
{ maxSeconds: 8, suffix: "8s" },
|
|
1562
|
+
{ maxSeconds: 12, suffix: "12s" },
|
|
1563
|
+
{ maxSeconds: 15, suffix: "15s" },
|
|
1564
|
+
],
|
|
1565
|
+
"seedance-2-fast": [
|
|
1566
|
+
{ maxSeconds: 4, suffix: "4s" },
|
|
1567
|
+
{ maxSeconds: 8, suffix: "8s" },
|
|
1568
|
+
{ maxSeconds: 12, suffix: "12s" },
|
|
1569
|
+
{ maxSeconds: 15, suffix: "15s" },
|
|
1570
|
+
],
|
|
1571
|
+
// Grok Imagine Video 1.5 — true per-second billing (KIE 14.5 cr/s @480p, 25 cr/s
|
|
1572
|
+
// @720p, +2 cr/image). One tier per allowed second (1–15s) so the composite
|
|
1573
|
+
// identifier maps 1:1 to the seeded price — no rounding/overcharge for any on-menu value.
|
|
1574
|
+
"grok-imagine-video-1.5": [
|
|
1575
|
+
{ maxSeconds: 1, suffix: "1s" },
|
|
1576
|
+
{ maxSeconds: 2, suffix: "2s" },
|
|
1577
|
+
{ maxSeconds: 3, suffix: "3s" },
|
|
1578
|
+
{ maxSeconds: 4, suffix: "4s" },
|
|
1579
|
+
{ maxSeconds: 5, suffix: "5s" },
|
|
1580
|
+
{ maxSeconds: 6, suffix: "6s" },
|
|
1581
|
+
{ maxSeconds: 7, suffix: "7s" },
|
|
1582
|
+
{ maxSeconds: 8, suffix: "8s" },
|
|
1583
|
+
{ maxSeconds: 9, suffix: "9s" },
|
|
1584
|
+
{ maxSeconds: 10, suffix: "10s" },
|
|
1585
|
+
{ maxSeconds: 11, suffix: "11s" },
|
|
1586
|
+
{ maxSeconds: 12, suffix: "12s" },
|
|
1587
|
+
{ maxSeconds: 13, suffix: "13s" },
|
|
1588
|
+
{ maxSeconds: 14, suffix: "14s" },
|
|
1589
|
+
{ maxSeconds: 15, suffix: "15s" },
|
|
1590
|
+
],
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
/**
|
|
1594
|
+
* Maps composer node types to their plan type identifier and the node data field
|
|
1595
|
+
* where the plan is stored. Used by render-video payload building, plan syncing,
|
|
1596
|
+
* and the frontend DAG executor.
|
|
1597
|
+
*/
|
|
1598
|
+
export const COMPOSER_PLAN_MAP: Readonly<Record<string, { planType: string; planField: string }>> = {
|
|
1599
|
+
"video-composer": { planType: "scene-graph", planField: "sceneGraph" },
|
|
1600
|
+
"after-effects": { planType: "after-effects", planField: "effectPlan" },
|
|
1601
|
+
"lottie-overlay": { planType: "lottie-overlay", planField: "overlayPlan" },
|
|
1602
|
+
"3d-title": { planType: "3d-title", planField: "titlePlan" },
|
|
1603
|
+
"motion-graphics": { planType: "motion-graphics", planField: "motionPlan" },
|
|
1604
|
+
"composite": { planType: "composite", planField: "compositePlan" },
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
/** Every composer plan-field name, derived from COMPOSER_PLAN_MAP (single source
|
|
1608
|
+
* of truth — consumed by preset plan-state hygiene and job-output extraction). */
|
|
1609
|
+
export const COMPOSER_PLAN_FIELDS: readonly string[] = [
|
|
1610
|
+
...new Set(Object.values(COMPOSER_PLAN_MAP).map((m) => m.planField)),
|
|
1611
|
+
]
|
|
1612
|
+
|
|
1613
|
+
/**
|
|
1614
|
+
* Duration tier breakpoints for motion control pricing (per-second billing).
|
|
1615
|
+
* Same shape as VIDEO_DURATION_TIERS entries but with 30s tier for long reference videos.
|
|
1616
|
+
*/
|
|
1617
|
+
export const MOTION_DURATION_TIERS: ReadonlyArray<{ maxSeconds: number; suffix: string }> = [
|
|
1618
|
+
{ maxSeconds: 5, suffix: "5s" },
|
|
1619
|
+
{ maxSeconds: 10, suffix: "10s" },
|
|
1620
|
+
{ maxSeconds: 15, suffix: "15s" },
|
|
1621
|
+
{ maxSeconds: 30, suffix: "30s" },
|
|
1622
|
+
]
|
|
1623
|
+
|
|
1624
|
+
/** Curated i2v providers compatible with character portrait reference input.
|
|
1625
|
+
* Subset of IMAGE_TO_VIDEO_PROVIDERS — excludes text-to-video-only models. */
|
|
1626
|
+
export const CHARACTER_MOTION_PROVIDERS = [
|
|
1627
|
+
"kling",
|
|
1628
|
+
"kling-turbo",
|
|
1629
|
+
"kling-3.0",
|
|
1630
|
+
"wan-i2v",
|
|
1631
|
+
"wan-2.7-i2v",
|
|
1632
|
+
] as const
|
|
1633
|
+
export type CharacterMotionProvider = typeof CHARACTER_MOTION_PROVIDERS[number]
|
|
1634
|
+
|
|
1635
|
+
/**
|
|
1636
|
+
* Atmospheric video providers available to the Location Studio's motion tab.
|
|
1637
|
+
*
|
|
1638
|
+
* Tuned for ambient camera moves (dolly, pan, drift, parallax) rather than
|
|
1639
|
+
* character-driven motion. Subset of registered i2v providers; `seedance-2`
|
|
1640
|
+
* added as the cinematic option for establishing shots.
|
|
1641
|
+
*
|
|
1642
|
+
* Mirrors `CHARACTER_MOTION_PROVIDERS` shape — see entity-prompts.ts for the
|
|
1643
|
+
* naming convention.
|
|
1644
|
+
*/
|
|
1645
|
+
export const LOCATION_ATMOSPHERE_PROVIDERS = [
|
|
1646
|
+
"kling",
|
|
1647
|
+
"kling-turbo",
|
|
1648
|
+
"kling-3.0",
|
|
1649
|
+
"wan-i2v",
|
|
1650
|
+
"wan-2.7-i2v",
|
|
1651
|
+
"seedance-2",
|
|
1652
|
+
] as const
|
|
1653
|
+
export type LocationAtmosphereProvider =
|
|
1654
|
+
(typeof LOCATION_ATMOSPHERE_PROVIDERS)[number]
|
|
1655
|
+
|
|
1656
|
+
/**
|
|
1657
|
+
* Subset of i2v providers tuned for short ambient object-motion clips (rotate,
|
|
1658
|
+
* hover, spin, parallax). Excludes premium models (Veo3, Veo3.1, Grok-i2v,
|
|
1659
|
+
* Sora2, Sora2-pro, Hailuo-2.3-pro) which are cost-prohibitive for the
|
|
1660
|
+
* product-loop use case (several times kling-turbo's per-clip cost).
|
|
1661
|
+
* Phase 2 candidate for adding veo3.1 if demand for premium hero shots
|
|
1662
|
+
* materializes.
|
|
1663
|
+
*/
|
|
1664
|
+
export const OBJECT_MOTION_PROVIDERS = [
|
|
1665
|
+
"kling-turbo", // 5s, fast, cheap (10 credits) — default
|
|
1666
|
+
"kling", // 5/10s
|
|
1667
|
+
"kling-3.0", // 3-15s, premium
|
|
1668
|
+
"minimax", // 5s, end-frame support
|
|
1669
|
+
"hailuo-2.3", // 6/10s
|
|
1670
|
+
"wan-i2v", // 5/10/15s
|
|
1671
|
+
"seedance", // 4/8/12s
|
|
1672
|
+
"bytedance-lite", // 5/10s
|
|
1673
|
+
] as const
|
|
1674
|
+
export type ObjectMotionProvider = (typeof OBJECT_MOTION_PROVIDERS)[number]
|
|
1675
|
+
|
|
1676
|
+
// =====================================================================
|
|
1677
|
+
// Scene Input Modes + Video Model Capability Registry (v4.0 + v4.1 spec §6.10)
|
|
1678
|
+
// =====================================================================
|
|
1679
|
+
|
|
1680
|
+
/**
|
|
1681
|
+
* SceneInputMode — the input-shape contract a video model exposes to the
|
|
1682
|
+
* Scene Director. Gates which `video_model` values are valid for a given
|
|
1683
|
+
* scene's `shot_input_mode`. Consumed by Scene Director LLM prompting,
|
|
1684
|
+
* route Zod validation, and the frontend SceneNode config panel.
|
|
1685
|
+
*
|
|
1686
|
+
* See architecture spec §6.10 + v4.1 Methods 2/3/8/10 for per-mode semantics.
|
|
1687
|
+
*/
|
|
1688
|
+
export const SceneInputModeSchema = z.enum([
|
|
1689
|
+
"text", // text-to-video
|
|
1690
|
+
"first_frame", // i2v with start image only
|
|
1691
|
+
"first_last_frame", // i2v with paired start+end (v4.1 Method 2)
|
|
1692
|
+
"ref_images", // multi-ref consistency models
|
|
1693
|
+
"multi_shot", // native multi-shot models (Kling Omni, Seedance multi)
|
|
1694
|
+
"video_continuation", // v4.1 Method 3 — extend prior clip (VEO extend, Seedance video-ref)
|
|
1695
|
+
"frame_interpolation", // v4.1 Method 8 — N sparse keyframes → interpolated video
|
|
1696
|
+
"camera_path", // v4.1 Method 10 — parametric 3D camera path
|
|
1697
|
+
])
|
|
1698
|
+
export type SceneInputMode = z.infer<typeof SceneInputModeSchema>
|
|
1699
|
+
|
|
1700
|
+
/**
|
|
1701
|
+
* Prompting style each model expects from the Scene Director.
|
|
1702
|
+
* Veo-family wants comma-separated cinematic tags; Kling-family wants
|
|
1703
|
+
* natural-language prose; Hailuo-family wants compact single-sentence prompts.
|
|
1704
|
+
*/
|
|
1705
|
+
export type ModelPromptingStyle =
|
|
1706
|
+
| "cinematic_tag_heavy" // Veo-family — comma-separated tags
|
|
1707
|
+
| "natural_language" // Kling-family — prose descriptions
|
|
1708
|
+
| "compact" // Hailuo-family — single-sentence
|
|
1709
|
+
|
|
1710
|
+
export interface VideoModelCapabilities {
|
|
1711
|
+
inputModes: SceneInputMode[]
|
|
1712
|
+
maxShotsPerCall?: number // for multi_shot models
|
|
1713
|
+
supportsLipSyncIntegrated?: boolean // Kling Avatar / VEO-3 etc
|
|
1714
|
+
supportsVideoExtension?: boolean // v4.1 Method 3
|
|
1715
|
+
supportsCameraPath?: boolean // v4.1 Method 10
|
|
1716
|
+
maxInterpolationKeyframes?: number // v4.1 Method 8
|
|
1717
|
+
maxDurationSeconds: number
|
|
1718
|
+
prompting_style: ModelPromptingStyle
|
|
1719
|
+
/**
|
|
1720
|
+
* Maximum number of reference IMAGES the model accepts in one call,
|
|
1721
|
+
* including the start frame. Drives `allocateReferenceSlots` in
|
|
1722
|
+
* `backend/src/ee/pipelines/continuity.ts` (Phase 1C.1 §5.13.3) — when
|
|
1723
|
+
* the budget is 1, multi-shot/character/object refs are silently
|
|
1724
|
+
* dropped and the slot allocator emits a `pipeline:warning` event.
|
|
1725
|
+
* Defaults to 1 when omitted.
|
|
1726
|
+
*/
|
|
1727
|
+
maxReferenceImages?: number
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
/**
|
|
1731
|
+
* VIDEO_MODEL_CAPS — single source of truth for what each video model supports.
|
|
1732
|
+
* Consumed by: Scene Director LLM (filters eligible models), route Zod validation
|
|
1733
|
+
* (rejects invalid video_model for the chosen shot_input_mode), frontend SceneNode
|
|
1734
|
+
* config panel (filters dropdown options).
|
|
1735
|
+
*
|
|
1736
|
+
* Adding a new video model: add an entry here, run audit-providers skill to verify
|
|
1737
|
+
* downstream wiring.
|
|
1738
|
+
*/
|
|
1739
|
+
export const VIDEO_MODEL_CAPS: Record<string, VideoModelCapabilities> = {
|
|
1740
|
+
"kling": {
|
|
1741
|
+
inputModes: ["first_frame", "first_last_frame", "text"],
|
|
1742
|
+
maxDurationSeconds: 10,
|
|
1743
|
+
prompting_style: "natural_language",
|
|
1744
|
+
maxReferenceImages: 1,
|
|
1745
|
+
},
|
|
1746
|
+
"kling-3-omni": {
|
|
1747
|
+
inputModes: ["ref_images", "multi_shot"],
|
|
1748
|
+
maxShotsPerCall: 5,
|
|
1749
|
+
maxDurationSeconds: 15,
|
|
1750
|
+
prompting_style: "natural_language",
|
|
1751
|
+
// Kling Omni accepts a strong multi-ref budget (start frame + up to
|
|
1752
|
+
// ~6 character/scene refs). Drives `allocateReferenceSlots` in
|
|
1753
|
+
// continuity.ts to surface all of them.
|
|
1754
|
+
maxReferenceImages: 7,
|
|
1755
|
+
},
|
|
1756
|
+
"veo3.1": {
|
|
1757
|
+
inputModes: ["first_frame", "text", "video_continuation"],
|
|
1758
|
+
supportsVideoExtension: true,
|
|
1759
|
+
maxDurationSeconds: 8,
|
|
1760
|
+
prompting_style: "cinematic_tag_heavy",
|
|
1761
|
+
maxReferenceImages: 1,
|
|
1762
|
+
},
|
|
1763
|
+
"seedance-2": {
|
|
1764
|
+
inputModes: ["first_frame", "first_last_frame", "ref_images", "video_continuation"],
|
|
1765
|
+
supportsVideoExtension: true,
|
|
1766
|
+
maxDurationSeconds: 10,
|
|
1767
|
+
prompting_style: "natural_language",
|
|
1768
|
+
// SEEDANCE_2_REF_LIMITS.images is the hard upper bound (9). 1C.1
|
|
1769
|
+
// caps to a more conservative 5 to leave headroom for primary
|
|
1770
|
+
// character + location + 3 secondary refs.
|
|
1771
|
+
maxReferenceImages: 5,
|
|
1772
|
+
},
|
|
1773
|
+
"hailuo-2.3-pro": {
|
|
1774
|
+
inputModes: ["first_frame", "text"],
|
|
1775
|
+
maxDurationSeconds: 10,
|
|
1776
|
+
prompting_style: "compact",
|
|
1777
|
+
maxReferenceImages: 1,
|
|
1778
|
+
},
|
|
1779
|
+
"hailuo-standard": {
|
|
1780
|
+
inputModes: ["first_frame", "first_last_frame"],
|
|
1781
|
+
maxDurationSeconds: 6,
|
|
1782
|
+
prompting_style: "compact",
|
|
1783
|
+
maxReferenceImages: 1,
|
|
1784
|
+
},
|
|
1785
|
+
"minimax": {
|
|
1786
|
+
inputModes: ["first_frame", "first_last_frame"],
|
|
1787
|
+
maxDurationSeconds: 6,
|
|
1788
|
+
prompting_style: "compact",
|
|
1789
|
+
maxReferenceImages: 1,
|
|
1790
|
+
},
|
|
1791
|
+
"kling-turbo": {
|
|
1792
|
+
inputModes: ["first_frame", "first_last_frame"],
|
|
1793
|
+
maxDurationSeconds: 10,
|
|
1794
|
+
prompting_style: "natural_language",
|
|
1795
|
+
maxReferenceImages: 1,
|
|
1796
|
+
},
|
|
1797
|
+
"bytedance-lite": {
|
|
1798
|
+
inputModes: ["first_frame", "first_last_frame"],
|
|
1799
|
+
maxDurationSeconds: 10,
|
|
1800
|
+
prompting_style: "compact",
|
|
1801
|
+
maxReferenceImages: 1,
|
|
1802
|
+
},
|
|
1803
|
+
// v4.1 Method 8 — Frame interpolation (sparse keyframes → interpolated video).
|
|
1804
|
+
// RIFE: Replicate-hosted, optical-flow-based interpolation, up to 8 keyframes.
|
|
1805
|
+
"rife": {
|
|
1806
|
+
inputModes: ["frame_interpolation"],
|
|
1807
|
+
maxInterpolationKeyframes: 8,
|
|
1808
|
+
maxDurationSeconds: 60,
|
|
1809
|
+
prompting_style: "compact",
|
|
1810
|
+
},
|
|
1811
|
+
// Topaz Apollo: cloud-hosted (Topaz.ai), higher keyframe budget.
|
|
1812
|
+
// Provider routing currently stubs as `provider_not_available:topaz-apollo`
|
|
1813
|
+
// pending KIE catalog check — entry kept so Shot List Critic surfaces it.
|
|
1814
|
+
"topaz-apollo": {
|
|
1815
|
+
inputModes: ["frame_interpolation"],
|
|
1816
|
+
maxInterpolationKeyframes: 16,
|
|
1817
|
+
maxDurationSeconds: 60,
|
|
1818
|
+
prompting_style: "compact",
|
|
1819
|
+
},
|
|
1820
|
+
// v4.1 Method 10 — Parametric 3D camera path (Stability AI SV3D via Replicate).
|
|
1821
|
+
// SV3D takes a single image + a camera path and renders a 3D-orbit video.
|
|
1822
|
+
"stable-video-3d": {
|
|
1823
|
+
inputModes: ["camera_path", "first_frame"],
|
|
1824
|
+
supportsCameraPath: true,
|
|
1825
|
+
maxDurationSeconds: 5,
|
|
1826
|
+
prompting_style: "natural_language",
|
|
1827
|
+
maxReferenceImages: 1,
|
|
1828
|
+
},
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
/**
|
|
1832
|
+
* modelsForInputMode — filter VIDEO_MODEL_CAPS to models that support the given mode.
|
|
1833
|
+
* Used at three call sites: Scene Director prompt construction, route Zod validation,
|
|
1834
|
+
* frontend config panel dropdown options.
|
|
1835
|
+
*/
|
|
1836
|
+
export const modelsForInputMode = (mode: SceneInputMode): string[] =>
|
|
1837
|
+
Object.entries(VIDEO_MODEL_CAPS)
|
|
1838
|
+
.filter(([, caps]) => caps.inputModes.includes(mode))
|
|
1839
|
+
.map(([model]) => model)
|
|
1840
|
+
|
|
1841
|
+
/**
|
|
1842
|
+
* preferredInputModeForModel — pick the best `shot_input_mode` for a pinned
|
|
1843
|
+
* video model so the pipeline adapts its input wiring to the chosen model:
|
|
1844
|
+
*
|
|
1845
|
+
* - models that accept `ref_images` (Seedance 2, Kling Omni) → `"ref_images"`,
|
|
1846
|
+
* so the character/location reference portraits feed the video model
|
|
1847
|
+
* directly (strongest identity lock).
|
|
1848
|
+
* - otherwise models that accept a start frame → `"first_frame"`, so the
|
|
1849
|
+
* per-scene keyframe is connected as the opening frame.
|
|
1850
|
+
*
|
|
1851
|
+
* Returns `undefined` for unknown/unregistered models (not in VIDEO_MODEL_CAPS)
|
|
1852
|
+
* or models that expose neither mode, so the caller keeps its own default.
|
|
1853
|
+
*
|
|
1854
|
+
* `first_last_frame` is deliberately NOT auto-selected — the animate stage
|
|
1855
|
+
* does not yet implement paired start+end keyframes (Method 2), so a
|
|
1856
|
+
* first_last_frame-capable model still resolves to `first_frame` here.
|
|
1857
|
+
*/
|
|
1858
|
+
export function preferredInputModeForModel(
|
|
1859
|
+
model: string | undefined,
|
|
1860
|
+
): SceneInputMode | undefined {
|
|
1861
|
+
if (!model) return undefined
|
|
1862
|
+
const caps = VIDEO_MODEL_CAPS[model]
|
|
1863
|
+
if (!caps) return undefined
|
|
1864
|
+
if (caps.inputModes.includes("ref_images")) return "ref_images"
|
|
1865
|
+
if (caps.inputModes.includes("first_frame")) return "first_frame"
|
|
1866
|
+
return undefined
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
1870
|
+
* Platform default video model (single source of truth)
|
|
1871
|
+
*
|
|
1872
|
+
* Applied wherever a video request omits `provider`: the generate-video and
|
|
1873
|
+
* text-to-video routes (credit resolution + dispatch), the DAG payload
|
|
1874
|
+
* builder, and the KIE provider's last-resort fallback. Chosen from usage
|
|
1875
|
+
* data (2026-07): most-selected video model with the highest completion rate.
|
|
1876
|
+
* ──────────────────────────────────────────────────────────────────────────── */
|
|
1877
|
+
|
|
1878
|
+
export const DEFAULT_VIDEO_PROVIDER = "seedance-2-fast"
|
|
1879
|
+
|
|
1880
|
+
/**
|
|
1881
|
+
* Duration paired with the default provider when BOTH provider and duration
|
|
1882
|
+
* are omitted. Pins the nothing-specified request to the default model's
|
|
1883
|
+
* cheapest seeded tier (4s @ 480p — resolution omission already resolves to
|
|
1884
|
+
* 480p in buildVideoCreditModelIdentifier), so first-touch cost stays at
|
|
1885
|
+
* entry level and longer clips are an explicit choice.
|
|
1886
|
+
*/
|
|
1887
|
+
export const DEFAULT_VIDEO_DURATION_SEC = 4
|
|
1888
|
+
|
|
1889
|
+
/**
|
|
1890
|
+
* Resolve the effective (provider, duration) for a video request.
|
|
1891
|
+
*
|
|
1892
|
+
* - provider omitted → DEFAULT_VIDEO_PROVIDER
|
|
1893
|
+
* - duration omitted AND provider was omitted → DEFAULT_VIDEO_DURATION_SEC
|
|
1894
|
+
* - an explicitly chosen provider keeps its own duration semantics (an
|
|
1895
|
+
* omitted duration falls through to that model's natural default)
|
|
1896
|
+
*
|
|
1897
|
+
* Used by both single-node routes AND the workflow payload builder so the
|
|
1898
|
+
* two execution paths cannot drift (the route/DAG default previously
|
|
1899
|
+
* disagreed: "minimax" vs "kling").
|
|
1900
|
+
*/
|
|
1901
|
+
export function applyDefaultVideoSelection<D>(input: {
|
|
1902
|
+
provider?: string | null
|
|
1903
|
+
duration?: D
|
|
1904
|
+
}): { provider: string; duration: D | number | undefined } {
|
|
1905
|
+
const providerOmitted = input.provider == null || input.provider === ""
|
|
1906
|
+
return {
|
|
1907
|
+
provider: providerOmitted ? DEFAULT_VIDEO_PROVIDER : (input.provider as string),
|
|
1908
|
+
duration: input.duration ?? (providerOmitted ? DEFAULT_VIDEO_DURATION_SEC : undefined),
|
|
1909
|
+
}
|
|
1910
|
+
}
|