@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,127 @@
|
|
|
1
|
+
import { INPUT_FIELD_MAP as PRESENTATION_INPUT_MAP } from "./presentation-utils.js"
|
|
2
|
+
|
|
3
|
+
export interface ComponentHandle {
|
|
4
|
+
id: string
|
|
5
|
+
name: string
|
|
6
|
+
type: "image" | "video" | "audio" | "text"
|
|
7
|
+
required: boolean
|
|
8
|
+
mediaPreview?: boolean
|
|
9
|
+
fieldKey: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ExposedSetting {
|
|
13
|
+
nodeId: string
|
|
14
|
+
field: string
|
|
15
|
+
label: string
|
|
16
|
+
type: "select" | "text" | "number" | "toggle" | "aspect-ratio"
|
|
17
|
+
allowedValues?: unknown[]
|
|
18
|
+
/** Full option list with labels for select fields (preferred over allowedValues). */
|
|
19
|
+
options?: Array<{ value: string; label: string }>
|
|
20
|
+
defaultValue: unknown
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ComponentMetadata {
|
|
24
|
+
inputs: ComponentHandle[]
|
|
25
|
+
outputs: ComponentHandle[]
|
|
26
|
+
exposedSettings: ExposedSetting[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Maps input node types to their data field key for inputOverrides (derived from presentation-utils) */
|
|
30
|
+
export const INPUT_FIELD_MAP: Record<string, string> = Object.fromEntries(
|
|
31
|
+
Object.entries(PRESENTATION_INPUT_MAP).map(([k, v]) => [k, v.key]),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
/** Separator used in component handle ids that target a specific port on a sub-workflow-input / sub-workflow-output node. */
|
|
35
|
+
export const HANDLE_PORT_SEPARATOR = "::"
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Split a component handle id of the form `nodeId` or `nodeId::portId`.
|
|
39
|
+
* Returns the literal nodeId when no separator is present.
|
|
40
|
+
*/
|
|
41
|
+
export function parseHandleId(handleId: string): { nodeId: string; portId?: string } {
|
|
42
|
+
const sep = handleId.indexOf(HANDLE_PORT_SEPARATOR)
|
|
43
|
+
if (sep < 0) return { nodeId: handleId }
|
|
44
|
+
const portId = handleId.slice(sep + HANDLE_PORT_SEPARATOR.length)
|
|
45
|
+
return { nodeId: handleId.slice(0, sep), portId: portId || undefined }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Apply a value for a component input handle to the inputOverrides map.
|
|
50
|
+
*
|
|
51
|
+
* - Plain handles (id = nodeId) write to `inputOverrides[nodeId][fieldKey]`,
|
|
52
|
+
* which the orchestrator shallow-merges into `node.data` at execution.
|
|
53
|
+
* - Compound handles (id = nodeId::portId) write into
|
|
54
|
+
* `inputOverrides[nodeId].__injectedPortValues[portId]`, the slot that
|
|
55
|
+
* sub-workflow-input nodes read at runtime via output-extractor.
|
|
56
|
+
*
|
|
57
|
+
* Mutates the map in place so multi-port accumulation merges correctly when
|
|
58
|
+
* iterating handles in a loop.
|
|
59
|
+
*/
|
|
60
|
+
export function applyHandleInputOverride(
|
|
61
|
+
inputOverrides: Record<string, Record<string, unknown>>,
|
|
62
|
+
handle: { id: string; fieldKey: string },
|
|
63
|
+
value: unknown,
|
|
64
|
+
): void {
|
|
65
|
+
const { nodeId, portId } = parseHandleId(handle.id)
|
|
66
|
+
if (portId) {
|
|
67
|
+
const existing = (inputOverrides[nodeId]?.__injectedPortValues as Record<string, unknown> | undefined) ?? {}
|
|
68
|
+
inputOverrides[nodeId] = {
|
|
69
|
+
...inputOverrides[nodeId],
|
|
70
|
+
__injectedPortValues: { ...existing, [portId]: value },
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
inputOverrides[nodeId] = { ...inputOverrides[nodeId], [handle.fieldKey]: value }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Has a value already been recorded for this handle in the inputOverrides map?
|
|
79
|
+
* Used to skip fallback wiring when an upstream connection already supplied
|
|
80
|
+
* the input.
|
|
81
|
+
*/
|
|
82
|
+
export function isHandleInputWired(
|
|
83
|
+
inputOverrides: Record<string, Record<string, unknown>>,
|
|
84
|
+
handle: { id: string; fieldKey: string },
|
|
85
|
+
): boolean {
|
|
86
|
+
const { nodeId, portId } = parseHandleId(handle.id)
|
|
87
|
+
if (portId) {
|
|
88
|
+
const injected = inputOverrides[nodeId]?.__injectedPortValues as Record<string, unknown> | undefined
|
|
89
|
+
return injected?.[portId] !== undefined
|
|
90
|
+
}
|
|
91
|
+
return inputOverrides[nodeId]?.[handle.fieldKey] !== undefined
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Maps output media types to NodeOutput field keys */
|
|
95
|
+
export const OUTPUT_FIELD_MAP: Record<string, string> = {
|
|
96
|
+
image: "imageUrl",
|
|
97
|
+
video: "videoUrl",
|
|
98
|
+
audio: "audioUrl",
|
|
99
|
+
text: "text",
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Merge exposed settings into inputOverrides format.
|
|
104
|
+
* exposedSettings is keyed by "nodeId:field" -> value on the component node data.
|
|
105
|
+
*
|
|
106
|
+
* Connected inputs (already present in inputOverrides) always take priority
|
|
107
|
+
* over exposed settings for the same node+field combination. This ensures
|
|
108
|
+
* that mandatory upstream wires are never silently overwritten by stale
|
|
109
|
+
* default / user-configured exposed-setting values.
|
|
110
|
+
*/
|
|
111
|
+
export function mergeExposedSettings(
|
|
112
|
+
inputOverrides: Record<string, Record<string, unknown>>,
|
|
113
|
+
exposedSettings: Record<string, unknown>,
|
|
114
|
+
metadata: ComponentMetadata,
|
|
115
|
+
): Record<string, Record<string, unknown>> {
|
|
116
|
+
const merged = { ...inputOverrides }
|
|
117
|
+
for (const setting of metadata.exposedSettings) {
|
|
118
|
+
const key = `${setting.nodeId}:${setting.field}`
|
|
119
|
+
const value = exposedSettings[key]
|
|
120
|
+
if (value !== undefined) {
|
|
121
|
+
// Skip if a connected input already provided this field
|
|
122
|
+
if (merged[setting.nodeId]?.[setting.field] !== undefined) continue
|
|
123
|
+
merged[setting.nodeId] = { ...merged[setting.nodeId], [setting.field]: value }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return merged
|
|
127
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a label→output map for `{Node Label}` refs inside condition values
|
|
3
|
+
* on filter-list and router nodes.
|
|
4
|
+
*
|
|
5
|
+
* Only considers edges whose `targetHandle === VARIABLES_HANDLE_ID` — the
|
|
6
|
+
* dedicated condition-variables input handle. Data-flow edges (targetHandle
|
|
7
|
+
* `"in"`, column handles, etc.) are ignored, so variable sources never
|
|
8
|
+
* influence the filtered/routed list.
|
|
9
|
+
*
|
|
10
|
+
* Duplicate labels are suffixed `Same`, `Same (2)`, `Same (3)` — matches
|
|
11
|
+
* the convention in `buildNodeRefMap` (frontend) / `buildNodeRefsMap`
|
|
12
|
+
* (backend) for text-prompt refs.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export const VARIABLES_HANDLE_ID = "variables"
|
|
16
|
+
|
|
17
|
+
type EdgeShape = {
|
|
18
|
+
source: string
|
|
19
|
+
target: string
|
|
20
|
+
sourceHandle?: string | null
|
|
21
|
+
targetHandle?: string | null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type NodeShape = {
|
|
25
|
+
id: string
|
|
26
|
+
type?: string
|
|
27
|
+
data: Record<string, unknown>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function buildConditionVariables<
|
|
31
|
+
E extends EdgeShape,
|
|
32
|
+
N extends NodeShape,
|
|
33
|
+
>(
|
|
34
|
+
targetNodeId: string,
|
|
35
|
+
edges: ReadonlyArray<E>,
|
|
36
|
+
nodes: ReadonlyArray<N>,
|
|
37
|
+
extractOutput: (node: N) => string | undefined,
|
|
38
|
+
): Map<string, string> {
|
|
39
|
+
const pairs: Array<{ label: string; output: string }> = []
|
|
40
|
+
for (const edge of edges) {
|
|
41
|
+
if (edge.target !== targetNodeId) continue
|
|
42
|
+
if (edge.targetHandle !== VARIABLES_HANDLE_ID) continue
|
|
43
|
+
const src = nodes.find((n) => n.id === edge.source)
|
|
44
|
+
if (!src) continue
|
|
45
|
+
const output = extractOutput(src)
|
|
46
|
+
if (output === undefined || output === "") continue
|
|
47
|
+
const label = (src.data.label as string) || src.type || src.id
|
|
48
|
+
pairs.push({ label, output })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const map = new Map<string, string>()
|
|
52
|
+
const counts = new Map<string, number>()
|
|
53
|
+
for (const { label } of pairs) counts.set(label, (counts.get(label) ?? 0) + 1)
|
|
54
|
+
const seen = new Map<string, number>()
|
|
55
|
+
for (const { label, output } of pairs) {
|
|
56
|
+
let key = label
|
|
57
|
+
if ((counts.get(label) ?? 0) > 1) {
|
|
58
|
+
const n = (seen.get(label) ?? 0) + 1
|
|
59
|
+
seen.set(label, n)
|
|
60
|
+
if (n > 1) key = `${label} (${n})`
|
|
61
|
+
}
|
|
62
|
+
map.set(key, output)
|
|
63
|
+
}
|
|
64
|
+
return map
|
|
65
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export {
|
|
2
|
+
VIDEO_UTIL_PRICING,
|
|
3
|
+
estimateLoopVideoCredits,
|
|
4
|
+
estimateTrimVideoCredits,
|
|
5
|
+
estimateCombineVideosCredits,
|
|
6
|
+
estimateLoopTrimAddonCredits,
|
|
7
|
+
assembleNarratedVideoCredits,
|
|
8
|
+
} from "./video-utils.js"
|
|
9
|
+
|
|
10
|
+
export type {
|
|
11
|
+
LoopVideoEstimatorInput,
|
|
12
|
+
TrimVideoEstimatorInput,
|
|
13
|
+
CombineVideosEstimatorInput,
|
|
14
|
+
LoopTrimEstimatorInput,
|
|
15
|
+
} from "./video-utils.js"
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure credit estimators for video-utility nodes (loop-video, trim-video,
|
|
3
|
+
* combine-videos). Used by frontend (Run-button display + API call) AND
|
|
4
|
+
* backend (creditGuard reservation) so the displayed cost equals what gets
|
|
5
|
+
* debited.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const VIDEO_UTIL_PRICING = {
|
|
9
|
+
/** 1 credit per 5 seconds of output, ceiling. */
|
|
10
|
+
CREDITS_PER_5_SEC: 1,
|
|
11
|
+
/** Smart-loop-cut work scales at 1 credit per ~24 frames searched. */
|
|
12
|
+
FRAMES_PER_CREDIT: 24,
|
|
13
|
+
/** Used when an upstream node hasn't produced a measurable duration yet. */
|
|
14
|
+
FALLBACK_DURATION_SECONDS: 8,
|
|
15
|
+
} as const
|
|
16
|
+
|
|
17
|
+
export interface LoopVideoEstimatorInput {
|
|
18
|
+
mode?: "repeat" | "duration"
|
|
19
|
+
repeatCount?: number
|
|
20
|
+
targetDuration?: number
|
|
21
|
+
smartLoopCutBeforeRepeat?: boolean
|
|
22
|
+
smartLoopCutLookback?: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TrimVideoEstimatorInput {
|
|
26
|
+
trimMode?: "time" | "seconds" | "keep-first-seconds" | "keep-last-seconds" | "frames" | "smart-loop-cut"
|
|
27
|
+
startTime?: number
|
|
28
|
+
endTime?: number
|
|
29
|
+
/** Seconds-mirror of frames mode: trim N seconds from start AND/OR end. */
|
|
30
|
+
trimStartSeconds?: number
|
|
31
|
+
trimEndSeconds?: number
|
|
32
|
+
/** Keep only the first/last N seconds (probes duration at runtime). */
|
|
33
|
+
keepFirstSeconds?: number
|
|
34
|
+
keepLastSeconds?: number
|
|
35
|
+
trimStartFrames?: number
|
|
36
|
+
trimEndFrames?: number
|
|
37
|
+
smartLoopCutLookback?: number
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface CombineVideosEstimatorInput {
|
|
41
|
+
/** Any id from `COMBINE_TRANSITIONS`. The estimator only branches on
|
|
42
|
+
* `cut` vs non-cut, so accepting the full string keeps the catalog as
|
|
43
|
+
* the single source of truth. */
|
|
44
|
+
transition?: string
|
|
45
|
+
transitionDuration?: number
|
|
46
|
+
trimStartFrames?: number
|
|
47
|
+
trimEndFrames?: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function estimateLoopVideoCredits(
|
|
51
|
+
data: LoopVideoEstimatorInput,
|
|
52
|
+
upstreamDuration?: number,
|
|
53
|
+
): number {
|
|
54
|
+
const fallback = VIDEO_UTIL_PRICING.FALLBACK_DURATION_SECONDS
|
|
55
|
+
const inputDuration = upstreamDuration ?? fallback
|
|
56
|
+
|
|
57
|
+
const output =
|
|
58
|
+
data.mode === "duration"
|
|
59
|
+
? (data.targetDuration ?? fallback)
|
|
60
|
+
: (data.repeatCount ?? 2) * inputDuration
|
|
61
|
+
|
|
62
|
+
const base = Math.ceil(output / 5)
|
|
63
|
+
const cut = data.smartLoopCutBeforeRepeat
|
|
64
|
+
? Math.ceil((data.smartLoopCutLookback ?? 16) / VIDEO_UTIL_PRICING.FRAMES_PER_CREDIT)
|
|
65
|
+
: 0
|
|
66
|
+
|
|
67
|
+
return Math.max(1, base + cut)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function estimateTrimVideoCredits(
|
|
71
|
+
data: TrimVideoEstimatorInput,
|
|
72
|
+
upstreamDuration?: number,
|
|
73
|
+
): number {
|
|
74
|
+
const fallback = VIDEO_UTIL_PRICING.FALLBACK_DURATION_SECONDS
|
|
75
|
+
const inputDuration = upstreamDuration ?? fallback
|
|
76
|
+
|
|
77
|
+
if (data.trimMode === "smart-loop-cut") {
|
|
78
|
+
const base = Math.ceil(inputDuration / 5)
|
|
79
|
+
const cut = Math.ceil((data.smartLoopCutLookback ?? 16) / VIDEO_UTIL_PRICING.FRAMES_PER_CREDIT)
|
|
80
|
+
return Math.max(1, base + cut)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (data.trimMode === "frames") {
|
|
84
|
+
// Source fps unknown frontend-side; assume 24.
|
|
85
|
+
const startSec = (data.trimStartFrames ?? 0) / VIDEO_UTIL_PRICING.FRAMES_PER_CREDIT
|
|
86
|
+
const endSec = (data.trimEndFrames ?? 0) / VIDEO_UTIL_PRICING.FRAMES_PER_CREDIT
|
|
87
|
+
const output = Math.max(0, inputDuration - startSec - endSec)
|
|
88
|
+
return Math.max(1, Math.ceil(output / 5))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (data.trimMode === "seconds") {
|
|
92
|
+
const output = Math.max(0, inputDuration - (data.trimStartSeconds ?? 0) - (data.trimEndSeconds ?? 0))
|
|
93
|
+
return Math.max(1, Math.ceil(output / 5))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (data.trimMode === "keep-first-seconds") {
|
|
97
|
+
const output = Math.min(inputDuration, Math.max(0, data.keepFirstSeconds ?? 0))
|
|
98
|
+
return Math.max(1, Math.ceil(output / 5))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (data.trimMode === "keep-last-seconds") {
|
|
102
|
+
const output = Math.min(inputDuration, Math.max(0, data.keepLastSeconds ?? 0))
|
|
103
|
+
return Math.max(1, Math.ceil(output / 5))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// "time" mode (default)
|
|
107
|
+
const output = (data.endTime ?? 0) - (data.startTime ?? 0)
|
|
108
|
+
return Math.max(1, Math.ceil(output / 5))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function estimateCombineVideosCredits(
|
|
112
|
+
data: CombineVideosEstimatorInput,
|
|
113
|
+
upstreamDurations: ReadonlyArray<number | undefined>,
|
|
114
|
+
): number {
|
|
115
|
+
if (upstreamDurations.length === 0) return 1
|
|
116
|
+
|
|
117
|
+
const fallback = VIDEO_UTIL_PRICING.FALLBACK_DURATION_SECONDS
|
|
118
|
+
const n = upstreamDurations.length
|
|
119
|
+
|
|
120
|
+
let total = 0
|
|
121
|
+
for (const d of upstreamDurations) {
|
|
122
|
+
total += typeof d === "number" && Number.isFinite(d) && d >= 0 ? d : fallback
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (data.transition && data.transition !== "cut" && n > 1) {
|
|
126
|
+
total -= (data.transitionDuration ?? 0.5) * (n - 1)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const trimSecPerClip =
|
|
130
|
+
((data.trimStartFrames ?? 0) + (data.trimEndFrames ?? 0)) /
|
|
131
|
+
VIDEO_UTIL_PRICING.FRAMES_PER_CREDIT
|
|
132
|
+
total -= trimSecPerClip * n
|
|
133
|
+
|
|
134
|
+
const base = Math.ceil(Math.max(0, total) / 5)
|
|
135
|
+
const inputAdder = Math.max(0, n - 2)
|
|
136
|
+
return Math.max(1, base + inputAdder)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface LoopTrimEstimatorInput {
|
|
140
|
+
enabled?: boolean
|
|
141
|
+
framesToTest?: number
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Add-on credits charged for the smart-loop-cut post-process applied to an
|
|
145
|
+
* image-to-video output. Returns 0 when loopTrim is undefined or disabled.
|
|
146
|
+
* Formula: ceil(duration / 5) + ceil(framesToTest / 24) — matches the
|
|
147
|
+
* trim-video smart-loop-cut formula for consistency. */
|
|
148
|
+
export function estimateLoopTrimAddonCredits(
|
|
149
|
+
loopTrim: LoopTrimEstimatorInput | undefined,
|
|
150
|
+
outputDurationSeconds: number,
|
|
151
|
+
): number {
|
|
152
|
+
if (!loopTrim?.enabled) return 0
|
|
153
|
+
const frames = Math.max(1, Math.min(loopTrim.framesToTest ?? 16, 64))
|
|
154
|
+
return Math.ceil(outputDurationSeconds / 5) +
|
|
155
|
+
Math.ceil(frames / VIDEO_UTIL_PRICING.FRAMES_PER_CREDIT)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** BASE credits (at-cost) for assemble-narrated-video: 3 flat + 1 per 6
|
|
159
|
+
* blocks. 6→4, 24→7, 60→13. Single source of truth shared by the backend
|
|
160
|
+
* route/creditGuard (`backend/src/providers/video/narrated-block-fit.ts`
|
|
161
|
+
* re-exports this) and the frontend pre-run estimate
|
|
162
|
+
* (`frontend/src/hooks/use-estimated-credits.ts`). */
|
|
163
|
+
export function assembleNarratedVideoCredits(blockCount: number): number {
|
|
164
|
+
return 3 + Math.ceil(blockCount / 6)
|
|
165
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build composite credit model identifiers for variable pricing.
|
|
3
|
+
* Shared between frontend and backend.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
HIGH_QUALITY_PROVIDERS,
|
|
8
|
+
TWO_K_RESOLUTION_PROVIDERS,
|
|
9
|
+
RESOLUTION_2K_4K_TIERED_PROVIDERS,
|
|
10
|
+
IDEOGRAM_PROVIDERS,
|
|
11
|
+
DURATION_PRICED_PROVIDERS,
|
|
12
|
+
AUDIO_ADDON_PROVIDERS,
|
|
13
|
+
MODE_ADDON_PROVIDERS,
|
|
14
|
+
RESOLUTION_VIDEO_REF_PRICING,
|
|
15
|
+
RESOLUTION_DURATION_PRICING,
|
|
16
|
+
VEO_RESOLUTION_TIERED_PROVIDERS,
|
|
17
|
+
VIDEO_DURATION_TIERS,
|
|
18
|
+
MOTION_DURATION_TIERS,
|
|
19
|
+
T2I_TO_I2I_VARIANT,
|
|
20
|
+
isVeoProvider,
|
|
21
|
+
} from "./model-constants.js"
|
|
22
|
+
import { isFlux2Model } from "./flux2-pricing.js"
|
|
23
|
+
import { MODEL_CATALOG } from "./model-catalog.js"
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Compute composite model identifier for variable credit pricing.
|
|
27
|
+
* Examples: "gpt-image:high", "flux:2K", "nano-banana-pro:4K", "ideogram:TURBO",
|
|
28
|
+
* "flux-2-pro:2MP", "flux-2-max:2MP:1ref" (Flux 2 — billed per megapixel).
|
|
29
|
+
*
|
|
30
|
+
* For image models, uses quality/resolution/renderingSpeed and (Flux 2) the output
|
|
31
|
+
* megapixel count plus (Flux 2 Max) the number of reference images attached at request time.
|
|
32
|
+
* For video models, uses duration/sound params when the model has variable pricing.
|
|
33
|
+
*/
|
|
34
|
+
export function buildCreditModelIdentifier(
|
|
35
|
+
provider: string,
|
|
36
|
+
quality?: string,
|
|
37
|
+
resolution?: string,
|
|
38
|
+
renderingSpeed?: string,
|
|
39
|
+
targetResolution?: string,
|
|
40
|
+
referenceImageCount?: number,
|
|
41
|
+
): string {
|
|
42
|
+
// Flux 2 family: billed per output megapixel AND per reference image (each
|
|
43
|
+
// ref counts as input megapixels). Resolution arrives as "N MP" strings (the
|
|
44
|
+
// UI value space); strip the unit. ALL three models encode the ref count
|
|
45
|
+
// (capped at 8): Pro/Klein i2i always carry the source image as a ref and
|
|
46
|
+
// their cost formula charges per input MP, so the reserved identifier must
|
|
47
|
+
// reflect refs (there is no metered true-up to correct an under-reserved tier).
|
|
48
|
+
if (isFlux2Model(provider)) {
|
|
49
|
+
const mp = (resolution ?? "1 MP").replace(/\s*MP$/i, "").trim()
|
|
50
|
+
return `${provider}:${mp}MP:${Math.min(referenceImageCount ?? 0, 8)}ref`
|
|
51
|
+
}
|
|
52
|
+
if (HIGH_QUALITY_PROVIDERS.has(provider) && quality === "high") {
|
|
53
|
+
return `${provider}:high`
|
|
54
|
+
}
|
|
55
|
+
if (TWO_K_RESOLUTION_PROVIDERS.has(provider) && resolution === "2K") {
|
|
56
|
+
return `${provider}:2K`
|
|
57
|
+
}
|
|
58
|
+
if (provider === "nano-banana-pro" && resolution === "4K") {
|
|
59
|
+
return `${provider}:4K`
|
|
60
|
+
}
|
|
61
|
+
if (RESOLUTION_2K_4K_TIERED_PROVIDERS.has(provider) && (resolution === "2K" || resolution === "4K")) {
|
|
62
|
+
return `${provider}:${resolution}`
|
|
63
|
+
}
|
|
64
|
+
// Topaz Image Upscale: 2K is default (no suffix), 4K/8K get composite identifiers
|
|
65
|
+
if (provider === "topaz-image-upscale" && targetResolution && targetResolution !== "2K") {
|
|
66
|
+
return `${provider}:${targetResolution}`
|
|
67
|
+
}
|
|
68
|
+
if (IDEOGRAM_PROVIDERS.has(provider)) {
|
|
69
|
+
if (renderingSpeed === "TURBO") return `${provider}:TURBO`
|
|
70
|
+
if (renderingSpeed === "QUALITY") return `${provider}:QUALITY`
|
|
71
|
+
}
|
|
72
|
+
return provider
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Reference-aware image-generation credit identifier — the SINGLE source of
|
|
77
|
+
* truth shared by the single-node routes (`/v1/generate-image`,
|
|
78
|
+
* `/v1/image-to-image`) and the workflow orchestrator
|
|
79
|
+
* (`payload-builder.ts`). It centralises the two pricing dimensions that must
|
|
80
|
+
* match across both paths or a Flux 2 generation is billed the wrong tier
|
|
81
|
+
* (Flux 2 commits non-metered / has no upward true-up, so the reserved
|
|
82
|
+
* identifier IS the final charge):
|
|
83
|
+
* - `refCount`: the number of reference images sent to the provider (Flux 2
|
|
84
|
+
* bills per ref). generate-image passes the assembled count (0 for pure
|
|
85
|
+
* text-to-image); image-to-image passes 1 (the primary `imageUrl`) + extras.
|
|
86
|
+
* - `swapToI2i`: generate-image auto-swaps a bare T2I provider to its i2i
|
|
87
|
+
* sibling (`T2I_TO_I2I_VARIANT`) when refs are attached, so the credit id
|
|
88
|
+
* matches the variant actually invoked. image-to-image is already an i2i
|
|
89
|
+
* provider → pass `false`.
|
|
90
|
+
*
|
|
91
|
+
* The LoRA short-circuit (`flux-lora-character`) stays at the call site because
|
|
92
|
+
* the routing decision needs a DB lookup the routes/orchestrator own.
|
|
93
|
+
*/
|
|
94
|
+
export function resolveImageGenCreditIdentifier(opts: {
|
|
95
|
+
provider: string | undefined
|
|
96
|
+
quality?: string
|
|
97
|
+
resolution?: string
|
|
98
|
+
renderingSpeed?: string
|
|
99
|
+
refCount: number
|
|
100
|
+
swapToI2i?: boolean
|
|
101
|
+
}): string {
|
|
102
|
+
const provider = opts.provider || "nano-banana"
|
|
103
|
+
const effectiveProvider =
|
|
104
|
+
opts.swapToI2i && opts.refCount > 0 ? (T2I_TO_I2I_VARIANT[provider] ?? provider) : provider
|
|
105
|
+
return buildCreditModelIdentifier(
|
|
106
|
+
effectiveProvider,
|
|
107
|
+
opts.quality,
|
|
108
|
+
opts.resolution,
|
|
109
|
+
opts.renderingSpeed,
|
|
110
|
+
undefined,
|
|
111
|
+
opts.refCount,
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// T2V-specific credit overrides: some providers have different costs for T2V
|
|
116
|
+
// vs I2V/V2V due to different default resolutions or colliding with image model names.
|
|
117
|
+
const T2V_CREDIT_OVERRIDES: Record<string, string> = {
|
|
118
|
+
"grok": "grok-i2v", // T2V grok, same as I2V grok (not image grok = 4 cr)
|
|
119
|
+
"wan": "wan-t2v", // T2V wan 1080p = (V2V wan 720p = 70 cr)
|
|
120
|
+
"wan-turbo": "wan-turbo-t2v", // T2V wan-turbo 720p = (I2V wan-turbo 480p = 40 cr)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Compute composite model identifier for video models with duration/audio-based pricing.
|
|
125
|
+
* Examples: "kling-3.0:5s", "kling-3.0:10s:audio", "seedance-2:8s:720p-ref"
|
|
126
|
+
*
|
|
127
|
+
* @param provider - Video model key (e.g., "kling-3.0")
|
|
128
|
+
* @param duration - Video duration in seconds
|
|
129
|
+
* @param sound - Whether audio/sound is enabled
|
|
130
|
+
* @param nodeType - Node type for T2V-specific cost overrides
|
|
131
|
+
* @param mode - Quality variant that affects pricing
|
|
132
|
+
* @param resolution - Output resolution (used by Seedance 2 for 480p/720p pricing)
|
|
133
|
+
* @param hasVideoRef - Whether a reference video is connected (Seedance 2 uses a lower per-second rate when true)
|
|
134
|
+
*/
|
|
135
|
+
/** Gemini Omni Video duration tiers (seconds). Mirrors `MODEL_CATALOG["gemini-omni-video"].durations`
|
|
136
|
+
* and `KIE_VIDEO_MODELS["gemini-omni-video"].allowedDurations`. Hoisted to module scope so the
|
|
137
|
+
* nearest-tier snap below doesn't reallocate on every (hot-path) call. */
|
|
138
|
+
const GEMINI_OMNI_DURATIONS = [4, 6, 8, 10]
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* LTX 2.3 (Lightricks via Replicate) credit tiers: priced by
|
|
142
|
+
* (resolution-band × duration-seconds). Keys mirror STATIC_CREDIT_COSTS /
|
|
143
|
+
* model_pricing EXACTLY (lowercase band + raw seconds + "s"), e.g.
|
|
144
|
+
* "ltx-2.3-pro:1080p:6s". This map is the source of truth for the nearest-tier
|
|
145
|
+
* snap below, so an off-catalog (resolution, duration) from a direct API/SDK
|
|
146
|
+
* call maps to a SEEDED price instead of hard-failing (price_not_configured
|
|
147
|
+
* 503). Pro: 6/8/10s at every band. Fast: up to 20s at 1080p, 6/8/10s at 2k/4k.
|
|
148
|
+
*/
|
|
149
|
+
const LTX_DURATION_TIERS: Record<string, Record<string, number[]>> = {
|
|
150
|
+
"ltx-2.3-pro": { "1080p": [6, 8, 10], "2k": [6, 8, 10], "4k": [6, 8, 10] },
|
|
151
|
+
"ltx-2.3-fast": { "1080p": [6, 8, 10, 12, 14, 16, 18, 20], "2k": [6, 8, 10], "4k": [6, 8, 10] },
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function buildVideoCreditModelIdentifier(
|
|
155
|
+
provider: string,
|
|
156
|
+
duration?: number | string,
|
|
157
|
+
sound?: boolean,
|
|
158
|
+
nodeType?: "image-to-video" | "text-to-video",
|
|
159
|
+
mode?: string,
|
|
160
|
+
resolution?: string,
|
|
161
|
+
hasVideoRef?: boolean,
|
|
162
|
+
): string {
|
|
163
|
+
// T2V overrides: some providers have different base costs for text-to-video
|
|
164
|
+
let effectiveProvider = provider
|
|
165
|
+
if (nodeType === "text-to-video") {
|
|
166
|
+
const override = T2V_CREDIT_OVERRIDES[provider]
|
|
167
|
+
if (override) {
|
|
168
|
+
// If override target also has duration pricing, use it as effective provider
|
|
169
|
+
if (DURATION_PRICED_PROVIDERS.has(override)) {
|
|
170
|
+
effectiveProvider = override
|
|
171
|
+
} else {
|
|
172
|
+
return override
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// VEO 3.x resolution-tiered pricing: 720p is the base (no suffix), 1080p gets
|
|
178
|
+
// ":1080p" (Fast/Lite only), and 4K gets ":4k" for ALL three tiers — direct-4K
|
|
179
|
+
// generation chains KIE's get-4k-video off the base generation (the base runs
|
|
180
|
+
// at 1080p). Duration is fixed at 8s for VEO so the duration-tier path below
|
|
181
|
+
// does not apply.
|
|
182
|
+
if (isVeoProvider(effectiveProvider)) {
|
|
183
|
+
if (resolution === "4k") return `${effectiveProvider}:4k`
|
|
184
|
+
// 1080p tier is Fast/Lite only (Quality has no 1080p surcharge).
|
|
185
|
+
if (resolution === "1080p" && VEO_RESOLUTION_TIERED_PROVIDERS.has(effectiveProvider)) return `${effectiveProvider}:1080p`
|
|
186
|
+
return effectiveProvider
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Gemini Omni Video: priced by (resolution-band × duration), with a flat
|
|
190
|
+
// per-generation rate when a source video is supplied (V2V). Lowercase "4k".
|
|
191
|
+
if (effectiveProvider === "gemini-omni-video") {
|
|
192
|
+
if (hasVideoRef) {
|
|
193
|
+
return resolution === "4k" ? "gemini-omni-video:4k:vref" : "gemini-omni-video:vref"
|
|
194
|
+
}
|
|
195
|
+
// Snap to nearest allowed tier (NOT a min/max clamp) so off-tier durations
|
|
196
|
+
// map to a SEEDED composite; default 8 when unset.
|
|
197
|
+
const raw = parseInt(String(duration ?? 8), 10)
|
|
198
|
+
const d = Number.isNaN(raw) ? 8 : GEMINI_OMNI_DURATIONS.reduce((b, a) => (Math.abs(a - raw) < Math.abs(b - raw) ? a : b))
|
|
199
|
+
return resolution === "4k" ? `gemini-omni-video:4k:${d}` : `gemini-omni-video:${d}`
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// LTX 2.3: priced by (resolution-band × duration-seconds). The RESERVE must be
|
|
203
|
+
// the correct composite — commit_credits (migration 176) only refunds a surplus
|
|
204
|
+
// (actual < reserved) and NEVER collects an upward delta, so an under-reserved
|
|
205
|
+
// LTX run (the bare-id default = cheapest 1080p:6s tier) stays under-charged
|
|
206
|
+
// even with meteredCost:true. Snap to a seeded tier so the id always prices.
|
|
207
|
+
if (effectiveProvider === "ltx-2.3-pro" || effectiveProvider === "ltx-2.3-fast") {
|
|
208
|
+
const bands = LTX_DURATION_TIERS[effectiveProvider]
|
|
209
|
+
const band = bands[String(resolution)] ? String(resolution) : "1080p"
|
|
210
|
+
const allowed = bands[band]
|
|
211
|
+
const raw = typeof duration === "string" ? parseInt(duration, 10) : (duration ?? allowed[0])
|
|
212
|
+
const want = Number.isNaN(raw) ? allowed[0] : raw
|
|
213
|
+
const dur = allowed.reduce((b, a) => (Math.abs(a - want) < Math.abs(b - want) ? a : b))
|
|
214
|
+
return `${effectiveProvider}:${band}:${dur}s`
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!DURATION_PRICED_PROVIDERS.has(effectiveProvider)) {
|
|
218
|
+
return effectiveProvider
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const parsed = typeof duration === "string" ? parseInt(duration, 10) : (duration ?? 5)
|
|
222
|
+
const durationSec = Number.isNaN(parsed) ? 5 : parsed
|
|
223
|
+
const tiers = VIDEO_DURATION_TIERS[effectiveProvider]
|
|
224
|
+
if (!tiers) return effectiveProvider
|
|
225
|
+
|
|
226
|
+
// Find the matching duration tier
|
|
227
|
+
const tier = tiers.find(t => durationSec <= t.maxSeconds) ?? tiers[tiers.length - 1]
|
|
228
|
+
let identifier = `${effectiveProvider}:${tier.suffix}`
|
|
229
|
+
|
|
230
|
+
// Append audio suffix if applicable
|
|
231
|
+
if (AUDIO_ADDON_PROVIDERS.has(effectiveProvider) && sound) {
|
|
232
|
+
identifier += ":audio"
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Append mode suffix for providers with quality-tiered pricing
|
|
236
|
+
// "high" comes from I2V videoSize field, "pro" comes from T2V mode field
|
|
237
|
+
if (MODE_ADDON_PROVIDERS.has(effectiveProvider) && (mode === "high" || mode === "pro")) {
|
|
238
|
+
identifier += ":high"
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Seedance 2.0 family: per-second billing with resolution + video-ref dimensions.
|
|
242
|
+
// KIE accepts 480p / 720p / 1080p natively; per-second rates rise non-linearly
|
|
243
|
+
// so each tier gets its own composite identifier.
|
|
244
|
+
if (RESOLUTION_VIDEO_REF_PRICING.has(effectiveProvider)) {
|
|
245
|
+
// Clamp to the model's catalog resolutions (single source of truth): e.g.
|
|
246
|
+
// seedance-2-mini exposes only 480p/720p, so a stale/explicit 1080p (or 4k)
|
|
247
|
+
// maps to its top priced tier instead of emitting an unpriced composite
|
|
248
|
+
// (which the hard-fail credit guard would 503 on at runtime).
|
|
249
|
+
const supported = MODEL_CATALOG[effectiveProvider]?.resolutions ?? ["480p", "720p", "1080p"]
|
|
250
|
+
const want = resolution === "4k" ? "4k" : resolution === "1080p" ? "1080p" : resolution === "720p" ? "720p" : "480p"
|
|
251
|
+
// Unsupported (e.g. a stale 1080p on seedance-2-mini) clamps to the model's
|
|
252
|
+
// top priced tier so the emitted composite is always seeded.
|
|
253
|
+
const res = supported.includes(want) ? want : (supported[supported.length - 1] ?? "480p")
|
|
254
|
+
identifier += `:${res}`
|
|
255
|
+
if (hasVideoRef) identifier += "-ref"
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Duration × resolution pricing without a -ref dimension. Resolutions
|
|
259
|
+
// outside the provider's priced tiers (or undefined) collapse to the first
|
|
260
|
+
// (default) tier so the emitted composite is always seeded — the hard-fail
|
|
261
|
+
// guard fuzzes the whole resolution space.
|
|
262
|
+
const resTiers = RESOLUTION_DURATION_PRICING[effectiveProvider]
|
|
263
|
+
if (resTiers) {
|
|
264
|
+
const res = resolution && resTiers.includes(resolution) ? resolution : resTiers[0]!
|
|
265
|
+
identifier += `:${res}`
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return identifier
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Compute composite model identifier for motion control with duration-tiered pricing.
|
|
273
|
+
* Examples: "kling-3.0-motion:10s", "kling-3.0-motion:1080p:15s", "motion-transfer:5s"
|
|
274
|
+
*
|
|
275
|
+
* Wan Animate providers use resolution-tiered pricing (not per-second):
|
|
276
|
+
* "wan-animate-move" (480p default), "wan-animate-move:580p", "wan-animate-move:720p"
|
|
277
|
+
*
|
|
278
|
+
* @param provider - Motion control provider key ("kling" for 2.6, "kling-3.0", "wan-animate-move", "wan-animate-replace")
|
|
279
|
+
* @param resolution - "720p" or "1080p" (Kling), "480p" or "580p" or "720p" (Wan Animate)
|
|
280
|
+
* @param videoDuration - Reference video duration in seconds (defaults to 10s, unused for Wan Animate)
|
|
281
|
+
*/
|
|
282
|
+
export function buildMotionCreditModelIdentifier(
|
|
283
|
+
provider: string,
|
|
284
|
+
resolution: string,
|
|
285
|
+
videoDuration?: number,
|
|
286
|
+
): string {
|
|
287
|
+
// Wan Animate providers use resolution-tiered pricing (not duration-based)
|
|
288
|
+
if (provider === "wan-animate-move" || provider === "wan-animate-replace") {
|
|
289
|
+
// 480p is the default (base identifier), 580p and 720p get composite suffix
|
|
290
|
+
if (resolution === "580p" || resolution === "720p") {
|
|
291
|
+
return `${provider}:${resolution}`
|
|
292
|
+
}
|
|
293
|
+
return provider
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const raw = videoDuration ?? 10
|
|
297
|
+
const durationSec = Math.floor(Number.isNaN(raw) ? 10 : raw) // default 10s; floor to match KIE per-second billing
|
|
298
|
+
const tier = MOTION_DURATION_TIERS.find(t => durationSec <= t.maxSeconds)
|
|
299
|
+
?? MOTION_DURATION_TIERS[MOTION_DURATION_TIERS.length - 1]
|
|
300
|
+
|
|
301
|
+
const base = provider === "kling-3.0" ? "kling-3.0-motion" : "motion-transfer"
|
|
302
|
+
const resSuffix = resolution === "1080p" ? ":1080p" : ""
|
|
303
|
+
return `${base}${resSuffix}:${tier.suffix}`
|
|
304
|
+
}
|