@nodaro/shared 1.4.2
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,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-linear progress calculation using quadratic ease-out curve.
|
|
3
|
+
* Returns 0–99 during execution, never reaching 100 (that's set on completion).
|
|
4
|
+
*
|
|
5
|
+
* Formula: progress = 99 * (1 - (1 - t)^exponent)
|
|
6
|
+
* where t = clamp(elapsedMs / estimatedMs, 0, 1)
|
|
7
|
+
*/
|
|
8
|
+
export function calculateProgress(
|
|
9
|
+
elapsedMs: number,
|
|
10
|
+
estimatedMs: number,
|
|
11
|
+
exponent: number = 2,
|
|
12
|
+
): number {
|
|
13
|
+
if (estimatedMs <= 0) return 0
|
|
14
|
+
const t = Math.min(Math.max(elapsedMs / estimatedMs, 0), 1)
|
|
15
|
+
return Math.round(99 * (1 - Math.pow(1 - t, exponent)))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Category-based fallback durations (ms) when no historical data exists. */
|
|
19
|
+
export const CATEGORY_DURATION_DEFAULTS: Record<string, number> = {
|
|
20
|
+
image: 30_000,
|
|
21
|
+
video: 120_000,
|
|
22
|
+
"audio-tts": 15_000,
|
|
23
|
+
music: 60_000,
|
|
24
|
+
llm: 8_000,
|
|
25
|
+
upscale: 30_000,
|
|
26
|
+
inline: 500,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Compute weighted progress segments for a multi-node flow.
|
|
31
|
+
* Each node gets a proportional slice of 0–99% based on its estimated duration.
|
|
32
|
+
*
|
|
33
|
+
* Returns array of { nodeId, startPct, endPct, estimatedMs } in execution order.
|
|
34
|
+
*/
|
|
35
|
+
export interface ProgressSegment {
|
|
36
|
+
nodeId: string
|
|
37
|
+
startPct: number
|
|
38
|
+
endPct: number
|
|
39
|
+
estimatedMs: number
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function buildProgressSegments(
|
|
43
|
+
nodeEstimates: { nodeId: string; estimatedMs: number }[],
|
|
44
|
+
): ProgressSegment[] {
|
|
45
|
+
const totalMs = nodeEstimates.reduce((sum, n) => sum + n.estimatedMs, 0)
|
|
46
|
+
if (totalMs <= 0) return []
|
|
47
|
+
|
|
48
|
+
const segments: ProgressSegment[] = []
|
|
49
|
+
let cursor = 0
|
|
50
|
+
|
|
51
|
+
for (let i = 0; i < nodeEstimates.length; i++) {
|
|
52
|
+
const { nodeId, estimatedMs } = nodeEstimates[i]
|
|
53
|
+
const sliceWidth = (estimatedMs / totalMs) * 99
|
|
54
|
+
const isLast = i === nodeEstimates.length - 1
|
|
55
|
+
segments.push({
|
|
56
|
+
nodeId,
|
|
57
|
+
startPct: cursor,
|
|
58
|
+
// Last segment always stretches to exactly 99
|
|
59
|
+
endPct: isLast ? 99 : cursor + sliceWidth,
|
|
60
|
+
estimatedMs,
|
|
61
|
+
})
|
|
62
|
+
cursor += sliceWidth
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return segments
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Calculate combined progress for a multi-node flow given current node states.
|
|
70
|
+
*/
|
|
71
|
+
export function calculateCombinedProgress(
|
|
72
|
+
segments: ProgressSegment[],
|
|
73
|
+
nodeStatuses: Record<string, {
|
|
74
|
+
status: "pending" | "running" | "completed" | "failed" | "skipped"
|
|
75
|
+
startedAt?: string
|
|
76
|
+
}>,
|
|
77
|
+
now: number = Date.now(),
|
|
78
|
+
): number {
|
|
79
|
+
let progress = 0
|
|
80
|
+
|
|
81
|
+
for (const seg of segments) {
|
|
82
|
+
const state = nodeStatuses[seg.nodeId]
|
|
83
|
+
if (!state) continue
|
|
84
|
+
|
|
85
|
+
const sliceWidth = seg.endPct - seg.startPct
|
|
86
|
+
|
|
87
|
+
if (state.status === "completed" || state.status === "skipped") {
|
|
88
|
+
progress += sliceWidth
|
|
89
|
+
} else if (state.status === "running" && state.startedAt) {
|
|
90
|
+
const elapsed = now - new Date(state.startedAt).getTime()
|
|
91
|
+
const nodeProgress = calculateProgress(elapsed, seg.estimatedMs)
|
|
92
|
+
// Map node's 0–99% to this segment's slice
|
|
93
|
+
progress += (nodeProgress / 99) * sliceWidth
|
|
94
|
+
}
|
|
95
|
+
// pending/failed contribute 0
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return Math.round(Math.min(progress, 99))
|
|
99
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { ShotSpec } from "./scene-node-types.js"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Provider-specific directive defaults. Architecture spec §5.14.
|
|
5
|
+
*
|
|
6
|
+
* Each video model can have side-effects the user usually wants (Seedance baking in
|
|
7
|
+
* ambient music, VEO supporting loop seeds, etc.). The engine applies these defaults
|
|
8
|
+
* at provider-call time, allowing them to be overridden by the Scene Director's
|
|
9
|
+
* `shot_intent` field (provider-neutral) which we map below.
|
|
10
|
+
*
|
|
11
|
+
* Keys are canonical provider identifiers (matching VIDEO_MODEL_CAPS in
|
|
12
|
+
* model-constants.ts). Non-canonical keys would silently break downstream
|
|
13
|
+
* route validation + payload building.
|
|
14
|
+
*/
|
|
15
|
+
export const PROVIDER_DIRECTIVE_DEFAULTS = {
|
|
16
|
+
seedance: { multishot: true, disable_internal_music: true, allow_sfx: true },
|
|
17
|
+
"seedance-2": { multishot: true, disable_internal_music: true, allow_sfx: true },
|
|
18
|
+
veo3: { reference_count: "auto" as const },
|
|
19
|
+
"veo3.1": { reference_count: "auto" as const },
|
|
20
|
+
kling: { reference_count: "auto" as const },
|
|
21
|
+
"kling-3-omni": { reference_count: "auto" as const },
|
|
22
|
+
"kling-turbo": { reference_count: "auto" as const },
|
|
23
|
+
hailuo: {} as Record<string, never>,
|
|
24
|
+
"hailuo-2.3-pro": {} as Record<string, never>,
|
|
25
|
+
"hailuo-standard": {} as Record<string, never>,
|
|
26
|
+
minimax: {} as Record<string, never>,
|
|
27
|
+
"bytedance-lite": {} as Record<string, never>,
|
|
28
|
+
} as const
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Maps a Scene Director's provider-neutral shot_intent to provider-specific directives.
|
|
32
|
+
* Architecture spec §5.14 mapping table.
|
|
33
|
+
*
|
|
34
|
+
* Unknown providers fall through to an empty object — the directive degrades to a text
|
|
35
|
+
* hint in the motion_prompt (Scene Director includes it there as a fallback when
|
|
36
|
+
* `needs_music_suppression: true`).
|
|
37
|
+
*/
|
|
38
|
+
export function mapShotIntentToProviderDirectives(
|
|
39
|
+
provider: string,
|
|
40
|
+
shotIntent: ShotSpec["shot_intent"],
|
|
41
|
+
): Record<string, unknown> {
|
|
42
|
+
const directives: Record<string, unknown> = {
|
|
43
|
+
...(PROVIDER_DIRECTIVE_DEFAULTS[provider as keyof typeof PROVIDER_DIRECTIVE_DEFAULTS] ?? {}),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (shotIntent.needs_multishot_reference) {
|
|
47
|
+
if (provider.startsWith("seedance")) {
|
|
48
|
+
directives.multishot = true
|
|
49
|
+
}
|
|
50
|
+
// VEO + Kling: accepts reference_images array — engine attaches the refs separately.
|
|
51
|
+
// Hailuo: only 1 ref — silently ignored at engine layer.
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (shotIntent.is_loopable) {
|
|
55
|
+
if (provider.startsWith("veo")) {
|
|
56
|
+
directives.loop_seed = "stable"
|
|
57
|
+
}
|
|
58
|
+
// Other providers: no native loop_seed → degrade to motion_prompt frame-matching hint.
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (shotIntent.needs_music_suppression) {
|
|
62
|
+
if (provider.startsWith("seedance")) {
|
|
63
|
+
directives.disable_internal_music = true
|
|
64
|
+
}
|
|
65
|
+
// VEO / Kling / Hailuo don't bake in music; no directive needed.
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return directives
|
|
69
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z, type ZodType, type ZodTypeDef } from "zod"
|
|
2
|
+
// Reuse the canonical `OutputType` from presentation-utils so a single union
|
|
3
|
+
// is the source of truth across the package. Re-exporting it here would
|
|
4
|
+
// collide with the explicit named re-export in `index.ts`.
|
|
5
|
+
import type { OutputType } from "./presentation-utils.js"
|
|
6
|
+
|
|
7
|
+
// `ZodType<Output, Def, Input>` with `Input = any` lets us accept schemas
|
|
8
|
+
// like `z.object({ x: z.string().default("") })` where the parsed output is
|
|
9
|
+
// `{ x: string }` but the input is `{ x?: string }`. Pinning Input to TConfig
|
|
10
|
+
// would force input === output and reject `.default()` schemas.
|
|
11
|
+
export type ReduceStrategy<TConfig = unknown> = {
|
|
12
|
+
readonly id: string
|
|
13
|
+
readonly label: string
|
|
14
|
+
readonly description: string
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
readonly configSchema: ZodType<TConfig, ZodTypeDef, any>
|
|
17
|
+
readonly defaultConfig: TConfig
|
|
18
|
+
readonly outputType: OutputType
|
|
19
|
+
readonly creditCostKey: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const PICK_BEST_LLM_STRATEGY = {
|
|
23
|
+
id: "pick-best-llm",
|
|
24
|
+
label: "Pick best (LLM judge)",
|
|
25
|
+
description: "Sonnet picks the best item against your criteria.",
|
|
26
|
+
configSchema: z.object({
|
|
27
|
+
// Default to the sensible "best quality" criteria when omitted (matches
|
|
28
|
+
// defaultConfig) so a reduce({strategyId:"pick-best-llm"}) call with no
|
|
29
|
+
// criteria degrades gracefully instead of erroring. An explicit "" still
|
|
30
|
+
// rejects via min(1).
|
|
31
|
+
criteria: z.string().min(1, "criteria cannot be empty").default("Pick the highest-quality result."),
|
|
32
|
+
inputKind: z.enum(["text", "image-url"]).default("text"),
|
|
33
|
+
}),
|
|
34
|
+
defaultConfig: { criteria: "Pick the highest-quality result.", inputKind: "text" as const },
|
|
35
|
+
outputType: "text" as OutputType,
|
|
36
|
+
creditCostKey: "reduce:pick-best-llm",
|
|
37
|
+
} as const satisfies ReduceStrategy<{ criteria: string; inputKind: "text" | "image-url" }>
|
|
38
|
+
|
|
39
|
+
const CONCAT_STRATEGY = {
|
|
40
|
+
id: "concat",
|
|
41
|
+
label: "Concatenate",
|
|
42
|
+
description: "Join all survivors with a separator.",
|
|
43
|
+
configSchema: z.object({ separator: z.string().default("\n\n") }),
|
|
44
|
+
defaultConfig: { separator: "\n\n" },
|
|
45
|
+
outputType: "text" as OutputType,
|
|
46
|
+
creditCostKey: "reduce:concat",
|
|
47
|
+
} as const satisfies ReduceStrategy<{ separator: string }>
|
|
48
|
+
|
|
49
|
+
const FIRST_NON_EMPTY_STRATEGY = {
|
|
50
|
+
id: "first-non-empty",
|
|
51
|
+
label: "First non-empty",
|
|
52
|
+
description: "Return the first survivor (empty strings filtered).",
|
|
53
|
+
configSchema: z.object({}),
|
|
54
|
+
defaultConfig: {},
|
|
55
|
+
outputType: "text" as OutputType,
|
|
56
|
+
creditCostKey: "reduce:first-non-empty",
|
|
57
|
+
} as const satisfies ReduceStrategy<Record<string, never>>
|
|
58
|
+
|
|
59
|
+
const COUNT_STRATEGY = {
|
|
60
|
+
id: "count",
|
|
61
|
+
label: "Count",
|
|
62
|
+
description: "Return how many survivors came through.",
|
|
63
|
+
configSchema: z.object({}),
|
|
64
|
+
defaultConfig: {},
|
|
65
|
+
outputType: "data" as OutputType,
|
|
66
|
+
creditCostKey: "reduce:count",
|
|
67
|
+
} as const satisfies ReduceStrategy<Record<string, never>>
|
|
68
|
+
|
|
69
|
+
const VOTE_STRATEGY = {
|
|
70
|
+
id: "vote",
|
|
71
|
+
label: "Majority vote",
|
|
72
|
+
description: "Return the most common survivor (ties → first).",
|
|
73
|
+
configSchema: z.object({ caseSensitive: z.boolean().default(false) }),
|
|
74
|
+
defaultConfig: { caseSensitive: false },
|
|
75
|
+
outputType: "text" as OutputType,
|
|
76
|
+
creditCostKey: "reduce:vote",
|
|
77
|
+
} as const satisfies ReduceStrategy<{ caseSensitive: boolean }>
|
|
78
|
+
|
|
79
|
+
const MERGE_JSON_STRATEGY = {
|
|
80
|
+
id: "merge-json",
|
|
81
|
+
label: "Merge JSON",
|
|
82
|
+
description: "Parse each survivor as JSON and merge into one object.",
|
|
83
|
+
configSchema: z.object({ strategy: z.enum(["deep", "shallow"]).default("deep") }),
|
|
84
|
+
defaultConfig: { strategy: "deep" as const },
|
|
85
|
+
outputType: "data" as OutputType,
|
|
86
|
+
creditCostKey: "reduce:merge-json",
|
|
87
|
+
} as const satisfies ReduceStrategy<{ strategy: "deep" | "shallow" }>
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Result-meta shape returned by every reduce strategy. Shared between the
|
|
91
|
+
* backend route, the SDK client, and the frontend node so all three layers
|
|
92
|
+
* agree on field names. `selectedIndex` + `reasoning` are set by the
|
|
93
|
+
* `pick-best-llm` and `vote` strategies (the former also fills `reasoning`);
|
|
94
|
+
* `summary` is always populated.
|
|
95
|
+
*/
|
|
96
|
+
export type ReduceMeta = {
|
|
97
|
+
readonly selectedIndex?: number
|
|
98
|
+
readonly reasoning?: string
|
|
99
|
+
readonly summary: string
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const REDUCE_STRATEGIES = [
|
|
103
|
+
PICK_BEST_LLM_STRATEGY,
|
|
104
|
+
CONCAT_STRATEGY,
|
|
105
|
+
FIRST_NON_EMPTY_STRATEGY,
|
|
106
|
+
COUNT_STRATEGY,
|
|
107
|
+
VOTE_STRATEGY,
|
|
108
|
+
MERGE_JSON_STRATEGY,
|
|
109
|
+
] as const
|
|
110
|
+
|
|
111
|
+
export const REDUCE_STRATEGY_IDS = REDUCE_STRATEGIES.map((s) => s.id) as readonly ReduceStrategyId[]
|
|
112
|
+
|
|
113
|
+
export type ReduceStrategyId = typeof REDUCE_STRATEGIES[number]["id"]
|
|
114
|
+
|
|
115
|
+
const BY_ID = new Map<string, ReduceStrategy>(REDUCE_STRATEGIES.map((s) => [s.id, s]))
|
|
116
|
+
|
|
117
|
+
export function getStrategy(id: ReduceStrategyId): ReduceStrategy {
|
|
118
|
+
const s = BY_ID.get(id)
|
|
119
|
+
if (!s) throw new Error(`unknown reduce strategy: ${id}`)
|
|
120
|
+
return s
|
|
121
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** Gen providers for the board: strict subset of IMAGE_GEN_PROVIDERS that
|
|
2
|
+
* renders legible in-image text + accepts reference conditioning. */
|
|
3
|
+
export const REFERENCE_BOARD_PROVIDERS = ["nano-banana-pro", "gpt-image-2"] as const
|
|
4
|
+
export type ReferenceBoardProvider = (typeof REFERENCE_BOARD_PROVIDERS)[number]
|
|
5
|
+
|
|
6
|
+
export type BoardEntityKind = "character" | "location" | "object"
|
|
7
|
+
|
|
8
|
+
export interface BoardTemplate {
|
|
9
|
+
/** "<entityKind>/<slug>" */
|
|
10
|
+
readonly id: string
|
|
11
|
+
readonly entityKind: BoardEntityKind
|
|
12
|
+
readonly label: string
|
|
13
|
+
readonly build: (ctx: BoardPromptContext) => string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Optional metadata the resolver supplies from a connected entity. All
|
|
17
|
+
* optional — the prompt degrades gracefully (omits labels) when absent. */
|
|
18
|
+
export interface BoardPromptContext {
|
|
19
|
+
readonly name?: string
|
|
20
|
+
readonly description?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ── Shared clauses (the three guide tricks) ──────────────────────────────
|
|
24
|
+
const ANTI_RIGID =
|
|
25
|
+
"Editorial reference-board layout, dark near-black background, thin yellow neon accent light, " +
|
|
26
|
+
"faint film-grain overlay, production-grade design UI. Organized but not rigidly locked: panels may " +
|
|
27
|
+
"shift position from generation to generation while staying readable, balanced and premium. Works " +
|
|
28
|
+
"cleanly in different aspect ratios without depending on a fixed left-column structure. All on-image labels in ENGLISH."
|
|
29
|
+
const PALETTE = "A COLOR PALETTE panel: 6 swatches with HEX codes derived from the dominant tones."
|
|
30
|
+
const CAPTION = (kind: string) =>
|
|
31
|
+
`Bottom caption: "Use this ${kind} board as a visual reference for consistent depiction across all generations." ` +
|
|
32
|
+
`Bottom-right tags: STYLE · Modern · Realistic · Cinematic. Style: photorealistic, no illustration. 8K, fine grain, cinematic color grading.`
|
|
33
|
+
|
|
34
|
+
const subj = (ctx: BoardPromptContext, fallback: string) =>
|
|
35
|
+
ctx.description?.trim() ? ctx.description.trim() : fallback
|
|
36
|
+
|
|
37
|
+
// ── Character ────────────────────────────────────────────────────────────
|
|
38
|
+
const characterFullBoard = (ctx: BoardPromptContext): string =>
|
|
39
|
+
`Create a single high-resolution, densely packed character reference sheet titled "CHARACTER BOARD" ` +
|
|
40
|
+
`using the attached photo as the single source of truth for face, hair, eyes, skin tone and body proportions ` +
|
|
41
|
+
`of ${subj(ctx, "the attached person")}. The same person appears in every panel — same age, same features. ${ANTI_RIGID} ` +
|
|
42
|
+
`Include a large hero portrait + metadata block: NAME · AGE · HEIGHT · BUILD · HAIR · EYES · FEATURES · OUTFIT · CHARACTER · MOOD. ` +
|
|
43
|
+
`Six panels: VIEWS (front · 3/4 · side · back) · EXPRESSIONS (5 headshots) · DETAILS (face/eyes, hand, outfit macro) · ` +
|
|
44
|
+
`OUTFIT FLAT-LAYS (5 isolated product shots) · LIGHTING/MOOD (4 lighting setups) · ${PALETTE} ${CAPTION("character")}`
|
|
45
|
+
|
|
46
|
+
// ── Location ─────────────────────────────────────────────────────────────
|
|
47
|
+
const locationFullBoard = (ctx: BoardPromptContext): string =>
|
|
48
|
+
`Create a single high-resolution, densely packed location reference sheet titled "LOCATION BOARD" ` +
|
|
49
|
+
`using the attached photo as the single source of truth for architecture, atmosphere, lighting, materials and color ` +
|
|
50
|
+
`of ${subj(ctx, "the attached location")}. The space is identical across every panel — same place, materials, era. ${ANTI_RIGID} ` +
|
|
51
|
+
`Include a hero establishing shot + metadata block: NAME · TYPE · ERA · SCALE · ARCHITECTURE · MATERIALS · ATMOSPHERE · DEFAULT TIME · DEFAULT WEATHER · PURPOSE. ` +
|
|
52
|
+
`Six panels: VIEWS (wide · mid · tight · alt · overhead) · TIME OF DAY (dawn · noon · dusk · night) · DETAILS (material, architectural) · ` +
|
|
53
|
+
`SET DRESSING / PROPS (5 studies) · WEATHER/MOOD (clear · overcast · rain · fog) · ${PALETTE} ${CAPTION("location")}`
|
|
54
|
+
|
|
55
|
+
// ── Object ───────────────────────────────────────────────────────────────
|
|
56
|
+
const objectFullBoard = (ctx: BoardPromptContext): string =>
|
|
57
|
+
`Create a single high-resolution, densely packed object reference sheet titled "OBJECT BOARD" ` +
|
|
58
|
+
`using the attached photo as the single source of truth for shape, materials, finish and proportions ` +
|
|
59
|
+
`of ${subj(ctx, "the attached object")}. The object is identical across every panel. ${ANTI_RIGID} ` +
|
|
60
|
+
`Include a hero shot + metadata block: NAME · TYPE · SCALE · MATERIALS · FINISH · KEY FEATURES · FUNCTION · MOOD. ` +
|
|
61
|
+
`Six panels: VIEWS (front · 3/4 · side · back · top) · MATERIALS (close studies) · DETAILS (key part macros) · ` +
|
|
62
|
+
`VARIATIONS (color/finish options) · LIGHTING/MOOD (4 setups) · ${PALETTE} ${CAPTION("object")}`
|
|
63
|
+
|
|
64
|
+
const t = (entityKind: BoardEntityKind, slug: string, label: string, build: BoardTemplate["build"]): BoardTemplate =>
|
|
65
|
+
({ id: `${entityKind}/${slug}`, entityKind, label, build })
|
|
66
|
+
|
|
67
|
+
export const REFERENCE_BOARD_TEMPLATES: readonly BoardTemplate[] = [
|
|
68
|
+
t("character", "full-board", "Full Board — hero + 6 panels + palette", characterFullBoard),
|
|
69
|
+
t("location", "full-board", "Full Board — hero + 6 panels + palette", locationFullBoard),
|
|
70
|
+
t("object", "full-board", "Full Board — hero + 6 panels + palette", objectFullBoard),
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
export function listBoardTemplates(kind: BoardEntityKind): BoardTemplate[] {
|
|
74
|
+
return REFERENCE_BOARD_TEMPLATES.filter((x) => x.entityKind === kind)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function buildBoardPrompt(templateId: string, ctx: BoardPromptContext): string {
|
|
78
|
+
const tpl = REFERENCE_BOARD_TEMPLATES.find((x) => x.id === templateId)
|
|
79
|
+
if (!tpl) throw new Error(`Unknown reference-board template: ${templateId}`)
|
|
80
|
+
return tpl.build(ctx)
|
|
81
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// packages/shared/src/reference-roles.ts
|
|
2
|
+
import { DEFAULT_LABEL_BY_SOURCE, type ReferenceSource } from "./types.js"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Per-source ORDERED preset role ids (most-useful first) for the editor's role
|
|
6
|
+
* menu. The single source of truth for the curated vocabulary — every menu and
|
|
7
|
+
* the resolver read from here. `Custom…` (free-form) is added by the UI, not
|
|
8
|
+
* listed here. Each list MUST contain the source's `DEFAULT_LABEL_BY_SOURCE`
|
|
9
|
+
* value (guarded by reference-roles.test.ts).
|
|
10
|
+
*/
|
|
11
|
+
export const REFERENCE_ROLE_PRESETS: Record<ReferenceSource, readonly string[]> = {
|
|
12
|
+
"wired-character": ["person", "face", "clothes", "hair", "pose", "expression", "style"],
|
|
13
|
+
"wired-face": ["face", "person", "expression", "style"],
|
|
14
|
+
"wired-location": ["background", "atmosphere", "as-is", "empty background", "layout", "lighting", "style"],
|
|
15
|
+
"wired-object": ["object", "shape", "material", "color", "texture", "style"],
|
|
16
|
+
"wired-creature": ["creature", "anatomy", "markings", "pose", "color", "style"],
|
|
17
|
+
"wired-image": ["object", "person", "face", "clothes", "background", "style", "pose", "texture"],
|
|
18
|
+
"manual": ["object", "person", "face", "clothes", "background", "style", "pose", "texture"],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Canonical default role for a source — the value the editor pre-fills and the
|
|
22
|
+
* resolver falls back to. Aliased to `DEFAULT_LABEL_BY_SOURCE` so the two never
|
|
23
|
+
* drift. */
|
|
24
|
+
export function defaultRoleForSource(source: ReferenceSource): string {
|
|
25
|
+
return DEFAULT_LABEL_BY_SOURCE[source]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Render the inline reference phrase. `binding` is the caller-formatted slot
|
|
30
|
+
* string: `"reference image A"` (image, lettered) or `"@image_3"` (video).
|
|
31
|
+
* Most roles are nouns and slot into the default template; the two non-noun
|
|
32
|
+
* specials get a hand-tuned phrasing so the prompt reads naturally.
|
|
33
|
+
*/
|
|
34
|
+
export function roleToPhrase(role: string, binding: string): string {
|
|
35
|
+
const r = role.trim()
|
|
36
|
+
if (!r) return binding
|
|
37
|
+
switch (r) {
|
|
38
|
+
case "as-is":
|
|
39
|
+
return `${binding}, used as-is`
|
|
40
|
+
case "empty background":
|
|
41
|
+
return `the background from ${binding} (without its foreground objects)`
|
|
42
|
+
default:
|
|
43
|
+
return `the ${r} from ${binding}`
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Map a location role SLUG — as it appears in a `@location:N:<slug>` mention
|
|
49
|
+
* token (lowercase, dash-joined, no spaces) — back to its canonical phrase key
|
|
50
|
+
* so `roleToPhrase` matches the non-noun specials. Only the multi-word
|
|
51
|
+
* `wired-location` presets need remapping (`empty-background` → `empty
|
|
52
|
+
* background`); single-token roles and `as-is` (whose phrase key keeps the
|
|
53
|
+
* hyphen) and free-form custom slugs pass through unchanged.
|
|
54
|
+
*
|
|
55
|
+
* Data-driven from `REFERENCE_ROLE_PRESETS["wired-location"]` (the single source
|
|
56
|
+
* of truth for the location vocabulary): a preset whose space→dash slug equals
|
|
57
|
+
* `slug` maps back to the preset's phrase form, so a future multi-word preset is
|
|
58
|
+
* handled with no extra wiring. Mention tokens are slug-form (the grammar
|
|
59
|
+
* segment is `[a-z][a-z0-9-]*`), so the role is stored verbatim on the token and
|
|
60
|
+
* normalized HERE at the consumption points — the location mention resolver
|
|
61
|
+
* (before `roleToPhrase`) and the location role pill (display label). The
|
|
62
|
+
* character side never needs this: character role slugs are single tokens that
|
|
63
|
+
* already equal their phrase key.
|
|
64
|
+
*/
|
|
65
|
+
export function normalizeRoleSlug(slug: string): string {
|
|
66
|
+
const s = slug.trim()
|
|
67
|
+
if (!s) return s
|
|
68
|
+
for (const preset of REFERENCE_ROLE_PRESETS["wired-location"]) {
|
|
69
|
+
if (preset.toLowerCase().replace(/\s+/g, "-") === s) return preset
|
|
70
|
+
}
|
|
71
|
+
return s
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Role for a FIRST-SIGHT character extra-ref in HYBRID assembly. The `segment`
|
|
76
|
+
* is the role when it is a curated preset for `source`; otherwise the source
|
|
77
|
+
* default. This mirrors the segment→role preset gate the mention-hybrid paths
|
|
78
|
+
* use, minus their custom-role-survival relaxation (an extra has no parsed
|
|
79
|
+
* `usageMode == null` signal to tell a role from a variant pick).
|
|
80
|
+
*
|
|
81
|
+
* Shared by BOTH the image (`renderExtraRefsHybrid` in `prompt-builder.ts`) and
|
|
82
|
+
* video (`resolveVideoReferenceCore` extras first-sight in
|
|
83
|
+
* `video-reference-resolver.ts`) resolvers — and, as of the Reference Roles
|
|
84
|
+
* deferred-follow-up, they share the HELPER **and** its INPUT, so they are
|
|
85
|
+
* fully converged:
|
|
86
|
+
* - image passes the COALESCED `defaultUsageMode`
|
|
87
|
+
* (`usageMode` → char-node default → "identical", folded by
|
|
88
|
+
* `expandExtraRefsToConnectedReferences`).
|
|
89
|
+
* - video passes its COALESCED `effectiveMode`
|
|
90
|
+
* (`ex.usageMode` → char-node default (`lookupCharacterBySlug`) →
|
|
91
|
+
* "identical") — the identical resolution chain, already computed for the
|
|
92
|
+
* legacy directive path.
|
|
93
|
+
* Both are always defined for a real character extra, so no `variantSlug`
|
|
94
|
+
* fallback is needed on either side. A character whose node default is
|
|
95
|
+
* Face/Pose/Style now resolves an un-overridden extra to that default on BOTH
|
|
96
|
+
* image and video (and consistently with the video legacy path). The single
|
|
97
|
+
* source of truth for the coalescing is the per-ref override → char-node
|
|
98
|
+
* default → "identical" chain, applied at both call sites.
|
|
99
|
+
*/
|
|
100
|
+
export function firstSightExtraRole(
|
|
101
|
+
segment: string | null | undefined,
|
|
102
|
+
source: ReferenceSource,
|
|
103
|
+
): string {
|
|
104
|
+
const s = (segment ?? "").trim()
|
|
105
|
+
return s && REFERENCE_ROLE_PRESETS[source].includes(s) ? s : defaultRoleForSource(source)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The effective default role for a wired entity, given the node's explicit
|
|
110
|
+
* hybrid `defaultRole` and its legacy `defaultUsageMode`. Single source of truth
|
|
111
|
+
* for the node-default precedence, read at every hybrid resolver site (image +
|
|
112
|
+
* video × extras / canonical / mention):
|
|
113
|
+
*
|
|
114
|
+
* 1. `defaultRole` (the hybrid dropdown pick) — used VERBATIM when non-blank,
|
|
115
|
+
* so a Custom role like `earrings` survives (mirrors the pill's Custom
|
|
116
|
+
* relaxation) and beats any legacy `defaultUsageMode`.
|
|
117
|
+
* 2. else `firstSightExtraRole(defaultUsageMode, source)` — the back-compat
|
|
118
|
+
* mapping (`face`/`pose`/`style` pass through; every other usage mode, and
|
|
119
|
+
* an absent one, collapse to the source default, e.g. `"person"`).
|
|
120
|
+
*
|
|
121
|
+
* A per-mention token role (the pill / autocomplete) still wins over this at the
|
|
122
|
+
* mention call sites; this is only the NODE-level fallback.
|
|
123
|
+
*/
|
|
124
|
+
export function resolveDefaultRole(
|
|
125
|
+
defaultRole: string | null | undefined,
|
|
126
|
+
defaultUsageMode: string | null | undefined,
|
|
127
|
+
source: ReferenceSource,
|
|
128
|
+
): string {
|
|
129
|
+
const explicit = defaultRole?.trim()
|
|
130
|
+
if (explicit) return explicit
|
|
131
|
+
return firstSightExtraRole(defaultUsageMode, source)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Sanitize a free-form Custom role into a character-variant-slug-safe token.
|
|
136
|
+
* Hoisted to shared (was `config-panels/prompt-editor/character-ref-roles.ts`)
|
|
137
|
+
* so BOTH the mention pill and the character node's role dropdown share one
|
|
138
|
+
* source of truth. Grammar is `[a-z][a-z0-9-]*` (the `variantSlug` token slot):
|
|
139
|
+
* lower-case, dash-join whitespace, drop out-of-grammar chars, force a leading
|
|
140
|
+
* letter, cap at 32, collapse dash runs, drop a trailing dash — keeping the
|
|
141
|
+
* emitted role re-parseable and matching `characterMentionSlug`'s slugification.
|
|
142
|
+
*/
|
|
143
|
+
export function sanitizeRole(raw: string): string {
|
|
144
|
+
return raw
|
|
145
|
+
.trim()
|
|
146
|
+
.toLowerCase()
|
|
147
|
+
.replace(/\s+/g, "-")
|
|
148
|
+
.replace(/[^a-z0-9-]/g, "")
|
|
149
|
+
.replace(/^[^a-z]+/, "") // grammar requires a leading [a-z]
|
|
150
|
+
.slice(0, 32)
|
|
151
|
+
.replace(/-+/g, "-") // collapse dash runs (matches characterMentionSlug)
|
|
152
|
+
.replace(/-$/, "") // drop a trailing dash (incl. one left by the 32-cap)
|
|
153
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import { BOARD_TO_COLUMN, BOARD_VARIANTS } from "../catalog.js"
|
|
3
|
+
import type { EntityKind } from "../types.js"
|
|
4
|
+
|
|
5
|
+
const ENTITIES: EntityKind[] = ["character", "object", "location"]
|
|
6
|
+
describe("BOARD_TO_COLUMN", () => {
|
|
7
|
+
it("maps character boards to DB columns", () => {
|
|
8
|
+
expect(BOARD_TO_COLUMN.character.headAngles).toBe("angles")
|
|
9
|
+
expect(BOARD_TO_COLUMN.character.bodyAngles).toBe("body_angles")
|
|
10
|
+
expect(BOARD_TO_COLUMN.character.detail).toBe("detail_closeups")
|
|
11
|
+
expect(BOARD_TO_COLUMN.character.wardrobe).toBe("outfit_variations")
|
|
12
|
+
})
|
|
13
|
+
it("maps location camelCase boards to snake_case columns", () => {
|
|
14
|
+
expect(BOARD_TO_COLUMN.location.timeOfDay).toBe("time_of_day")
|
|
15
|
+
expect(BOARD_TO_COLUMN.location.detail).toBe("detail_closeups")
|
|
16
|
+
})
|
|
17
|
+
it("every board in BOARD_VARIANTS has a column (no orphan)", () => {
|
|
18
|
+
for (const e of ENTITIES) for (const b of Object.keys(BOARD_VARIANTS[e])) expect(BOARD_TO_COLUMN[e][b], `${e}/${b}`).toBeDefined()
|
|
19
|
+
})
|
|
20
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import {
|
|
3
|
+
ENTITY_SECTIONS, BOARD_VARIANTS, SECTION_BOARD, DEFAULT_SECTIONS, DETAIL_VARIANTS, WARDROBE_VARIANTS,
|
|
4
|
+
} from "../catalog.js"
|
|
5
|
+
import { SECTION_KINDS, SHEET_TYPES, type EntityKind } from "../types.js"
|
|
6
|
+
import { CHARACTER_ASSET_VARIANTS, OBJECT_ASSET_VARIANTS, LOCATION_ASSET_VARIANTS } from "../../entity-asset-types.js"
|
|
7
|
+
|
|
8
|
+
const ENTITIES: EntityKind[] = ["character", "object", "location"]
|
|
9
|
+
|
|
10
|
+
describe("catalog invariants", () => {
|
|
11
|
+
it("every entity's available sections are valid SectionKinds", () => {
|
|
12
|
+
for (const e of ENTITIES) for (const s of ENTITY_SECTIONS[e]) expect(SECTION_KINDS).toContain(s)
|
|
13
|
+
})
|
|
14
|
+
it("character board variants come from the canonical CHARACTER_ASSET_VARIANTS", () => {
|
|
15
|
+
expect(BOARD_VARIANTS.character.headAngles).toEqual(CHARACTER_ASSET_VARIANTS.headAngles)
|
|
16
|
+
expect(BOARD_VARIANTS.character.bodyAngles).toEqual(CHARACTER_ASSET_VARIANTS.bodyAngles)
|
|
17
|
+
expect(BOARD_VARIANTS.character.expressions).toEqual(CHARACTER_ASSET_VARIANTS.expressions)
|
|
18
|
+
expect(BOARD_VARIANTS.character.poses).toEqual(CHARACTER_ASSET_VARIANTS.poses)
|
|
19
|
+
})
|
|
20
|
+
it("object & location board variants come from the hoisted lists", () => {
|
|
21
|
+
expect(BOARD_VARIANTS.object.angles).toEqual(OBJECT_ASSET_VARIANTS.angles)
|
|
22
|
+
expect(BOARD_VARIANTS.object.materials).toEqual(OBJECT_ASSET_VARIANTS.materials)
|
|
23
|
+
expect(BOARD_VARIANTS.location.timeOfDay).toEqual(LOCATION_ASSET_VARIANTS.timeOfDay)
|
|
24
|
+
expect(BOARD_VARIANTS.location.angles).toEqual(LOCATION_ASSET_VARIANTS.angles)
|
|
25
|
+
})
|
|
26
|
+
it("headAngles includes back-of-head, appended after the default-4 slice", () => {
|
|
27
|
+
const head = CHARACTER_ASSET_VARIANTS.headAngles
|
|
28
|
+
expect(head).toContain("back")
|
|
29
|
+
expect(head.indexOf("back")).toBeGreaterThanOrEqual(4) // keeps slice(0,4) stable
|
|
30
|
+
expect(head.slice(0, 4)).toEqual(["front", "3/4 left", "left profile", "right profile"])
|
|
31
|
+
})
|
|
32
|
+
it("new buckets (detail/wardrobe) have starter presets", () => {
|
|
33
|
+
expect(DETAIL_VARIANTS.character.length).toBeGreaterThan(0)
|
|
34
|
+
expect(WARDROBE_VARIANTS.length).toBeGreaterThan(0)
|
|
35
|
+
})
|
|
36
|
+
it("DEFAULT_SECTIONS exists for every (entity, type) and only references available sections", () => {
|
|
37
|
+
for (const e of ENTITIES) for (const t of SHEET_TYPES) {
|
|
38
|
+
const secs = DEFAULT_SECTIONS[e][t]
|
|
39
|
+
expect(Array.isArray(secs)).toBe(true)
|
|
40
|
+
for (const s of secs) expect(ENTITY_SECTIONS[e]).toContain(s.kind)
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
it("every board-typed section a default uses resolves to a real board with variants", () => {
|
|
44
|
+
for (const e of ENTITIES) for (const t of SHEET_TYPES) for (const s of DEFAULT_SECTIONS[e][t]) {
|
|
45
|
+
const board = s.board ?? SECTION_BOARD[s.kind]
|
|
46
|
+
if (board === null) continue // structural section (header/palette/notes/scale)
|
|
47
|
+
expect(BOARD_VARIANTS[e][board!], `${e}/${t}/${s.kind} -> ${board}`).toBeDefined()
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import { estimateSheetCost } from "../cost.js"
|
|
3
|
+
import { SHEET_PRESETS } from "../presets.js"
|
|
4
|
+
import type { SheetFlavour, SheetSection } from "../types.js"
|
|
5
|
+
|
|
6
|
+
const fl: SheetFlavour = { outputFormat: "still", withText: true, showLabels: true, aspect: "landscape", background: "grey" }
|
|
7
|
+
const main = SHEET_PRESETS.find((p) => p.id === "studio-main")!
|
|
8
|
+
|
|
9
|
+
describe("estimateSheetCost", () => {
|
|
10
|
+
it("all panels missing → prepareCost = missing × perPanel, total += assembly", () => {
|
|
11
|
+
const e = estimateSheetCost("character", main.baseSections, fl, {}, "Kaia", 1, 4)
|
|
12
|
+
expect(e.missing).toHaveLength(7)
|
|
13
|
+
expect(e.present).toBe(0)
|
|
14
|
+
expect(e.prepareCost).toBe(7)
|
|
15
|
+
expect(e.total).toBe(11)
|
|
16
|
+
expect(e.overflow).toBe(false)
|
|
17
|
+
})
|
|
18
|
+
it("present panels are not re-charged", () => {
|
|
19
|
+
const buckets = { angles: [{ name: "front", url: "u/f" }], body_angles: [{ name: "front", url: "u/bf" }] }
|
|
20
|
+
const e = estimateSheetCost("character", main.baseSections, fl, buckets, "Kaia", 1, 4)
|
|
21
|
+
expect(e.present).toBe(2)
|
|
22
|
+
expect(e.prepareCost).toBe(5)
|
|
23
|
+
expect(e.total).toBe(9)
|
|
24
|
+
})
|
|
25
|
+
it("overflow (> MAX_PANELS) returns overflow:true instead of throwing", () => {
|
|
26
|
+
// 25 explicit entries > MAX_PANELS_PER_SHEET (24) → planSheetPanels throws;
|
|
27
|
+
// estimateSheetCost must catch it. (panelCount would clamp to the board
|
|
28
|
+
// length and never overflow, so use entries to force it.)
|
|
29
|
+
const huge: SheetSection[] = [{
|
|
30
|
+
kind: "head-turnaround",
|
|
31
|
+
entries: Array.from({ length: 25 }, (_, i) => ({ kind: "preset" as const, variant: `v${i}` })),
|
|
32
|
+
}]
|
|
33
|
+
const e = estimateSheetCost("character", huge, fl, {}, "Kaia", 1, 4)
|
|
34
|
+
expect(e.overflow).toBe(true)
|
|
35
|
+
expect(e.missing).toHaveLength(0)
|
|
36
|
+
expect(e.total).toBe(4) // assembly only
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import { BOARD_VARIANTS, DEFAULT_SECTIONS, SECTION_BOARD } from "../catalog.js"
|
|
3
|
+
import { planSheetPanels } from "../panel-plan.js"
|
|
4
|
+
import { SHEET_TYPES, SECTION_KINDS, type EntityKind, type SheetFlavour } from "../types.js"
|
|
5
|
+
|
|
6
|
+
const ENTITIES: EntityKind[] = ["character", "object", "location"]
|
|
7
|
+
const fl: SheetFlavour = { outputFormat: "still", withText: true, showLabels: true, aspect: "landscape", background: "grey" }
|
|
8
|
+
|
|
9
|
+
describe("no-drift: planned preset panels are real board variants", () => {
|
|
10
|
+
it("every default full-reference plans without throwing and only uses known variants", () => {
|
|
11
|
+
for (const e of ENTITIES) for (const t of SHEET_TYPES) {
|
|
12
|
+
const panels = planSheetPanels(e, DEFAULT_SECTIONS[e][t], fl)
|
|
13
|
+
for (const p of panels) {
|
|
14
|
+
if (p.custom) continue
|
|
15
|
+
expect(BOARD_VARIANTS[e][p.board], `${e}/${t} ${p.board}`).toContain(p.variant)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
it("SECTION_BOARD has an entry for every SectionKind (no kind missing)", () => {
|
|
20
|
+
// Guards `resolveBoard`: adding a SectionKind without a SECTION_BOARD entry
|
|
21
|
+
// would make `SECTION_BOARD[kind]` undefined and silently mis-resolve. Iterate
|
|
22
|
+
// the full SECTION_KINDS list — not just the ones used in defaults — so a new
|
|
23
|
+
// kind can't slip in unmapped.
|
|
24
|
+
for (const k of SECTION_KINDS) {
|
|
25
|
+
expect(SECTION_BOARD, k).toHaveProperty(k)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
})
|