@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,102 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Character aspect-ratio defaults
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// Characters are vertical subjects — the historical 16:9 default produced wide,
|
|
6
|
+
// crop-unfriendly portraits. The per-asset-type table below picks the ratio
|
|
7
|
+
// that fits each asset's natural framing:
|
|
8
|
+
//
|
|
9
|
+
// - Portrait / canonical head shot → 3:4
|
|
10
|
+
// - Expressions (head-and-shoulders) → 1:1
|
|
11
|
+
// - Poses (full-body) → 9:16
|
|
12
|
+
// - Head angles → 3:4
|
|
13
|
+
// - Body angles (full-body) → 9:16
|
|
14
|
+
// - Lighting variations (full-body context) → 3:4
|
|
15
|
+
// - Motions (vertical clip, full-body i2v) → 9:16
|
|
16
|
+
//
|
|
17
|
+
// Resolution precedence (low → high):
|
|
18
|
+
// 1. Per-asset-type default (this table)
|
|
19
|
+
// 2. Character node's `defaultAssetAspectRatio` (user override on the canvas
|
|
20
|
+
// node — flows in as `characterNodeAspectRatio` in the request body)
|
|
21
|
+
// 3. Explicit `aspectRatio` on the API request (caller wins)
|
|
22
|
+
//
|
|
23
|
+
// The character node's `defaultAssetAspectRatio` toggle (1:1 / 3:4 / 16:9 /
|
|
24
|
+
// 9:16) drives BOTH the canvas thumbnail crop AND this override slot.
|
|
25
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
export const CHARACTER_ASPECT_OPTIONS = ["1:1", "3:4", "16:9", "9:16"] as const
|
|
28
|
+
export type CharacterAspectRatio = (typeof CHARACTER_ASPECT_OPTIONS)[number]
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The asset categories with distinct natural framing. Aliases:
|
|
32
|
+
* - `angles` is the legacy single-surface key (now head-and-shoulders by
|
|
33
|
+
* default). Use `headAngles` for clarity in new code.
|
|
34
|
+
* - `bodyAngles` is the new full-body column added in migration 118.
|
|
35
|
+
*/
|
|
36
|
+
export type CharacterAssetTypeForAspect =
|
|
37
|
+
| "portrait"
|
|
38
|
+
| "expressions"
|
|
39
|
+
| "poses"
|
|
40
|
+
| "angles"
|
|
41
|
+
| "headAngles"
|
|
42
|
+
| "bodyAngles"
|
|
43
|
+
| "lighting"
|
|
44
|
+
| "motions"
|
|
45
|
+
|
|
46
|
+
export const CHARACTER_ASPECT_DEFAULTS: Record<
|
|
47
|
+
CharacterAssetTypeForAspect,
|
|
48
|
+
CharacterAspectRatio
|
|
49
|
+
> = {
|
|
50
|
+
portrait: "3:4",
|
|
51
|
+
// Legacy `angles` was head angles — keep its default aligned with `headAngles`.
|
|
52
|
+
angles: "3:4",
|
|
53
|
+
headAngles: "3:4",
|
|
54
|
+
// Full-body framings:
|
|
55
|
+
poses: "9:16",
|
|
56
|
+
bodyAngles: "9:16",
|
|
57
|
+
motions: "9:16",
|
|
58
|
+
// Expressions are head-and-shoulders — square framing matches the existing
|
|
59
|
+
// expression card grid and avoids cutting off forehead/chin.
|
|
60
|
+
expressions: "1:1",
|
|
61
|
+
// Lighting variations are framed like portraits (3:4) — they're full-body in
|
|
62
|
+
// the prompt but the user usually wants to see the lighting on the face.
|
|
63
|
+
lighting: "3:4",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function isCharacterAspectRatio(s: string): s is CharacterAspectRatio {
|
|
67
|
+
return (CHARACTER_ASPECT_OPTIONS as readonly string[]).includes(s)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ResolveCharacterAspectOptions {
|
|
71
|
+
/** Explicit `aspectRatio` on the request body — highest precedence. */
|
|
72
|
+
readonly explicit?: string | null
|
|
73
|
+
/**
|
|
74
|
+
* The character node's `defaultAssetAspectRatio` — set per-canvas-node via
|
|
75
|
+
* the 4-pill toggle on the character node. Flows through the API as
|
|
76
|
+
* `characterNodeAspectRatio`.
|
|
77
|
+
*/
|
|
78
|
+
readonly nodeOverride?: string | null
|
|
79
|
+
/** What's being generated. Drives the default when nothing else is set. */
|
|
80
|
+
readonly assetType: CharacterAssetTypeForAspect
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Resolve the final aspect ratio for a character generation request.
|
|
85
|
+
*
|
|
86
|
+
* Precedence: explicit > node override > per-asset-type default. The two
|
|
87
|
+
* "soft" inputs are tolerated as raw strings and only honored when they
|
|
88
|
+
* parse to a value in `CHARACTER_ASPECT_OPTIONS` — anything else falls
|
|
89
|
+
* through to the next layer (so a stale enum value doesn't poison the
|
|
90
|
+
* generation).
|
|
91
|
+
*/
|
|
92
|
+
export function resolveCharacterAspectRatio(
|
|
93
|
+
opts: ResolveCharacterAspectOptions,
|
|
94
|
+
): CharacterAspectRatio {
|
|
95
|
+
if (opts.explicit && isCharacterAspectRatio(opts.explicit)) {
|
|
96
|
+
return opts.explicit
|
|
97
|
+
}
|
|
98
|
+
if (opts.nodeOverride && isCharacterAspectRatio(opts.nodeOverride)) {
|
|
99
|
+
return opts.nodeOverride
|
|
100
|
+
}
|
|
101
|
+
return CHARACTER_ASPECT_DEFAULTS[opts.assetType]
|
|
102
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Facets of a source character/asset that can be selectively injected into a
|
|
3
|
+
* consumer character via the Character node's Assets handle (element/asset
|
|
4
|
+
* injection, P2). Single source of truth for the frontend facet chip options
|
|
5
|
+
* AND the backend facet-extraction instructions.
|
|
6
|
+
*
|
|
7
|
+
* `full` injects the whole source description verbatim (no LLM call needed).
|
|
8
|
+
* Every other facet is LLM-extracted on demand from the source's
|
|
9
|
+
* `canonicalDescription` via `POST /v1/character/extract-facet`.
|
|
10
|
+
*/
|
|
11
|
+
export interface CharacterFacet {
|
|
12
|
+
/** Stable id stored on the connection (`assetInjections[].facet`). */
|
|
13
|
+
readonly id: string
|
|
14
|
+
/** Human label shown in the facet chip. */
|
|
15
|
+
readonly label: string
|
|
16
|
+
/** What the extractor should pull from the source description. Phrased to
|
|
17
|
+
* complete "Extract {instruction} …". Unused for `full`. */
|
|
18
|
+
readonly instruction: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const CHARACTER_FACETS: readonly CharacterFacet[] = [
|
|
22
|
+
{ id: "full", label: "Full likeness", instruction: "the complete physical likeness and appearance" },
|
|
23
|
+
{ id: "hair", label: "Hair", instruction: "ONLY the hair — style, length, colour, and texture" },
|
|
24
|
+
{ id: "skin-tone", label: "Skin tone", instruction: "ONLY the skin tone / complexion" },
|
|
25
|
+
{ id: "face", label: "Face", instruction: "ONLY the facial features — face shape, eyes, nose, mouth, eyebrows, and any distinguishing marks" },
|
|
26
|
+
{ id: "style", label: "Style", instruction: "ONLY the overall visual style, aesthetic, and vibe — not specific garments" },
|
|
27
|
+
{ id: "outfit", label: "Outfit", instruction: "ONLY the clothing, outfit, and wardrobe" },
|
|
28
|
+
{ id: "personality", label: "Personality", instruction: "ONLY the personality, demeanour, and characteristic expression" },
|
|
29
|
+
] as const
|
|
30
|
+
|
|
31
|
+
export const DEFAULT_CHARACTER_FACET = "full"
|
|
32
|
+
|
|
33
|
+
export const CHARACTER_FACET_IDS: ReadonlySet<string> = new Set(CHARACTER_FACETS.map((f) => f.id))
|
|
34
|
+
|
|
35
|
+
export function getCharacterFacet(id: string): CharacterFacet | undefined {
|
|
36
|
+
return CHARACTER_FACETS.find((f) => f.id === id)
|
|
37
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { isUsageMode, type UsageMode } from "./character-usage-mode.js"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Slugify a name (character or variant) for use in @-mention tokens.
|
|
5
|
+
* Lowercase, strip non-alphanumeric, collapse runs of dashes.
|
|
6
|
+
*/
|
|
7
|
+
export function characterMentionSlug(name: string): string {
|
|
8
|
+
return name
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
11
|
+
.replace(/-+/g, "-")
|
|
12
|
+
.replace(/^-|-$/g, "")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CharacterMentionTokenInfo {
|
|
16
|
+
readonly token: string
|
|
17
|
+
readonly characterSlug: string
|
|
18
|
+
/**
|
|
19
|
+
* 1-based positional index assigned at insertion time by the autocomplete.
|
|
20
|
+
* Lets the user wire a literal-text mention (`@kira:1`) to a final-prompt
|
|
21
|
+
* identity directive (`Image 1 (person) — match exactly…`) so the order in
|
|
22
|
+
* the typed prompt matches the order in the assembled directive block.
|
|
23
|
+
*/
|
|
24
|
+
readonly imageIndex: number
|
|
25
|
+
readonly variantSlug: string | null
|
|
26
|
+
/**
|
|
27
|
+
* Optional per-mention usage-mode override (e.g. `:face`, `:style`). When
|
|
28
|
+
* `null`, the assembled directive falls back to the source character node's
|
|
29
|
+
* `defaultUsageMode`, then to the global `"identical"` default. Always one
|
|
30
|
+
* of the entries in `USAGE_MODES` when non-null — the parser rejects any
|
|
31
|
+
* other 4-part token.
|
|
32
|
+
*/
|
|
33
|
+
readonly usageMode: UsageMode | null
|
|
34
|
+
/**
|
|
35
|
+
* Additive per-mention identity-lock sentinel (Unified Reference Roles,
|
|
36
|
+
* Task 4 + F4). Tri-state: `true` (trailing `~lock`, force lock ON) |
|
|
37
|
+
* `false` (trailing `~nolock`, force lock OFF — suppresses a ref-level
|
|
38
|
+
* `identityLock.enabled = true`) | ABSENT/undefined (neither sentinel —
|
|
39
|
+
* inherit the ref/source default; byte-identical to the pre-Task-4 shape).
|
|
40
|
+
* Only ACTED UPON by the HYBRID resolvers; the LEGACY resolvers ignore it
|
|
41
|
+
* entirely, so both `~lock` and `~nolock` are inert on the legacy path.
|
|
42
|
+
*/
|
|
43
|
+
readonly lock?: boolean
|
|
44
|
+
readonly offset: number
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parse a single `@<character>:<index>(:<variant>)?(:<usageMode>)?` token.
|
|
49
|
+
*
|
|
50
|
+
* Supported shapes (each segment after `:` may contain internal dashes, so
|
|
51
|
+
* `@young-kira:1:soft-smile` parses unambiguously as character + index +
|
|
52
|
+
* variant):
|
|
53
|
+
*
|
|
54
|
+
* - `@kira:1` → canonical, default mode
|
|
55
|
+
* - `@kira:1:smile` → variant smile, default mode
|
|
56
|
+
* - `@kira:1:face` → canonical, mode "face" (mode is detected by
|
|
57
|
+
* checking the 3rd segment against `USAGE_MODES`)
|
|
58
|
+
* - `@kira:1:smile:face` → variant smile, mode "face"
|
|
59
|
+
* - `@kira:1:smile:bogus` → null (4-part rejected when last segment is not
|
|
60
|
+
* a valid usage mode — keeps the format strict)
|
|
61
|
+
*
|
|
62
|
+
* Format change: as of the index-in-slug update, the index segment is required.
|
|
63
|
+
* Bare `@kira` is not a mention; the autocomplete always inserts at least
|
|
64
|
+
* `@kira:N`.
|
|
65
|
+
*
|
|
66
|
+
* `knownCharacterSlugs` is no longer needed for parsing (the structure is
|
|
67
|
+
* unambiguous given the closed `USAGE_MODES` enum) — it remains accepted for
|
|
68
|
+
* API compatibility but ignored here. `findCharacterMentionTokens` still uses
|
|
69
|
+
* it to filter unknown characters.
|
|
70
|
+
*/
|
|
71
|
+
export function parseCharacterMentionToken(
|
|
72
|
+
text: string,
|
|
73
|
+
_knownCharacterSlugs?: readonly string[],
|
|
74
|
+
): { characterSlug: string; imageIndex: number; variantSlug: string | null; usageMode: UsageMode | null; lock?: boolean } | null {
|
|
75
|
+
if (!text.startsWith("@")) return null
|
|
76
|
+
let rest = text.slice(1)
|
|
77
|
+
if (rest.length === 0 || !/^[a-z]/.test(rest)) return null
|
|
78
|
+
|
|
79
|
+
// Additive `~lock` / `~nolock` sentinels (Unified Reference Roles, Task 4 +
|
|
80
|
+
// F4). Strip a trailing `~nolock` (force lock OFF) or `~lock` (force lock ON)
|
|
81
|
+
// BEFORE splitting on ":" so the segment grammar below is byte-identical to
|
|
82
|
+
// pre-Task-4 (a `~` can never appear inside a segment — the shapes are
|
|
83
|
+
// `[a-z][a-z0-9-]*`). Check `~nolock` FIRST: `"x~nolock".endsWith("~lock")` is
|
|
84
|
+
// false (last 5 chars "olock"), but the explicit ordering keeps intent clear.
|
|
85
|
+
// A token with NEITHER sentinel is untouched here and gains NO `lock` key
|
|
86
|
+
// (byte-identical). The parsed tri-state `lock` (true/false/undefined) is only
|
|
87
|
+
// honored by the HYBRID resolvers — undefined inherits the ref default, true
|
|
88
|
+
// forces the lock on, false forces it off.
|
|
89
|
+
let lockField: { lock?: boolean } = {}
|
|
90
|
+
if (rest.endsWith("~nolock")) {
|
|
91
|
+
rest = rest.slice(0, -"~nolock".length)
|
|
92
|
+
lockField = { lock: false }
|
|
93
|
+
} else if (rest.endsWith("~lock")) {
|
|
94
|
+
rest = rest.slice(0, -"~lock".length)
|
|
95
|
+
lockField = { lock: true }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Format: <character>:<index>(:<variant|mode>)?(:<mode>)?.
|
|
99
|
+
// Splitting on ":" requires 2–4 parts (no bare @character anymore).
|
|
100
|
+
const parts = rest.split(":")
|
|
101
|
+
if (parts.length < 2 || parts.length > 4) return null
|
|
102
|
+
|
|
103
|
+
const [characterSlug, indexStr, third, fourth] = parts
|
|
104
|
+
if (!/^[a-z][a-z0-9-]*$/.test(characterSlug)) return null
|
|
105
|
+
if (!/^\d+$/.test(indexStr)) return null
|
|
106
|
+
const imageIndex = parseInt(indexStr, 10)
|
|
107
|
+
if (!Number.isInteger(imageIndex) || imageIndex < 1) return null
|
|
108
|
+
|
|
109
|
+
// 2-part: kira:1 — canonical, default mode.
|
|
110
|
+
if (parts.length === 2) {
|
|
111
|
+
return { characterSlug, imageIndex, variantSlug: null, usageMode: null, ...lockField }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 3-part: kira:1:X — X is either a usage-mode keyword or a variant slug.
|
|
115
|
+
// The closed `USAGE_MODES` enum + the variant-slug shape constraint
|
|
116
|
+
// (`[a-z][a-z0-9-]*`) makes this unambiguous. A keyword like "face" wins
|
|
117
|
+
// the mode interpretation; anything else is treated as a variant slug.
|
|
118
|
+
if (parts.length === 3) {
|
|
119
|
+
if (!/^[a-z][a-z0-9-]*$/.test(third)) return null
|
|
120
|
+
if (isUsageMode(third)) {
|
|
121
|
+
return { characterSlug, imageIndex, variantSlug: null, usageMode: third, ...lockField }
|
|
122
|
+
}
|
|
123
|
+
return { characterSlug, imageIndex, variantSlug: third, usageMode: null, ...lockField }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 4-part: kira:1:smile:mode — variant + mode override. Both segments must
|
|
127
|
+
// satisfy their respective shape; an unknown mode kills the token entirely
|
|
128
|
+
// (callers fall back to literal text, matching unknown-character behavior).
|
|
129
|
+
if (parts.length === 4) {
|
|
130
|
+
if (!/^[a-z][a-z0-9-]*$/.test(third)) return null
|
|
131
|
+
if (!isUsageMode(fourth)) return null
|
|
132
|
+
return { characterSlug, imageIndex, variantSlug: third, usageMode: fourth, ...lockField }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Find all @-mentions in a prompt that match a known character slug. */
|
|
139
|
+
export function findCharacterMentionTokens(
|
|
140
|
+
prompt: string,
|
|
141
|
+
knownCharacterSlugs: readonly string[],
|
|
142
|
+
): CharacterMentionTokenInfo[] {
|
|
143
|
+
const tokens: CharacterMentionTokenInfo[] = []
|
|
144
|
+
// `@<character>:<index>(:<variant|mode>)?(:<mode>)?` preceded by
|
|
145
|
+
// non-alphanumeric (or start of string) to prevent email-like matches.
|
|
146
|
+
// The `\d+` index segment is required — see `parseCharacterMentionToken`
|
|
147
|
+
// for the rationale. The two optional trailing `:<slug>` groups absorb
|
|
148
|
+
// either `:<variant>`, `:<mode>`, or `:<variant>:<mode>`; the parser
|
|
149
|
+
// disambiguates which segment is which based on `USAGE_MODES`. The optional
|
|
150
|
+
// trailing `(?:~(?:no)?lock)?` absorbs the additive Task-4/F4 identity-lock
|
|
151
|
+
// sentinel (`~lock` force-on OR `~nolock` force-off) INTO the token (so it is
|
|
152
|
+
// spliced out at resolve time); it is optional, so a lock-less token matches
|
|
153
|
+
// byte-identically to before. The `(?![a-z0-9-])` word boundary keeps
|
|
154
|
+
// `~locked` / `~nolockx` literal.
|
|
155
|
+
const regex = /(?:^|[^a-zA-Z0-9])(@[a-z][a-z0-9-]*:\d+(?::[a-z][a-z0-9-]*)?(?::[a-z][a-z0-9-]*)?(?:~(?:no)?lock(?![a-z0-9-]))?)/g
|
|
156
|
+
const knownSet = new Set(knownCharacterSlugs)
|
|
157
|
+
for (const match of prompt.matchAll(regex)) {
|
|
158
|
+
const token = match[1]
|
|
159
|
+
const offset = (match.index ?? 0) + (match[0].length - token.length)
|
|
160
|
+
const parsed = parseCharacterMentionToken(token)
|
|
161
|
+
if (parsed && knownSet.has(parsed.characterSlug)) {
|
|
162
|
+
tokens.push({ token, ...parsed, offset })
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return tokens
|
|
166
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Character-mention usage modes — describe HOW the AI should consume a
|
|
3
|
+
* referenced character image. The mode shapes the per-image directive in the
|
|
4
|
+
* assembled prompt (see `usageModeDirective`) so the same character reference
|
|
5
|
+
* can act as a full likeness replacement, a face-only crop, a style donor,
|
|
6
|
+
* etc., without needing different upstream images.
|
|
7
|
+
*
|
|
8
|
+
* The mode source-of-truth lives in two places:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Character node default** — `CharacterNodeData.defaultUsageMode`.
|
|
11
|
+
* Set via the small dropdown in the character node's title row.
|
|
12
|
+
* Propagates into every `ConnectedReference` derived from that node.
|
|
13
|
+
*
|
|
14
|
+
* 2. **Per-mention override** — optional 4th segment in the slug,
|
|
15
|
+
* e.g. `@kira:1:smile:face` overrides the character's default for that
|
|
16
|
+
* specific mention. Parsed by `parseCharacterMentionToken` in
|
|
17
|
+
* `character-mention-slug.ts`.
|
|
18
|
+
*
|
|
19
|
+
* Mode resolution at prompt-build time (in `resolveCharacterMentions`):
|
|
20
|
+
* token.usageMode ?? ref.defaultUsageMode ?? "identical"
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export const USAGE_MODES = [
|
|
24
|
+
"identical",
|
|
25
|
+
"face",
|
|
26
|
+
"face-pose",
|
|
27
|
+
"pose",
|
|
28
|
+
"emotion",
|
|
29
|
+
"style",
|
|
30
|
+
"name",
|
|
31
|
+
"none",
|
|
32
|
+
] as const
|
|
33
|
+
|
|
34
|
+
export type UsageMode = (typeof USAGE_MODES)[number]
|
|
35
|
+
|
|
36
|
+
/** Default mode when neither the slug nor the character node specifies one. */
|
|
37
|
+
export const DEFAULT_USAGE_MODE: UsageMode = "identical"
|
|
38
|
+
|
|
39
|
+
/** Type guard — narrows an arbitrary string to `UsageMode` when valid. */
|
|
40
|
+
export function isUsageMode(s: string): s is UsageMode {
|
|
41
|
+
return (USAGE_MODES as readonly string[]).includes(s)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Return the natural-language directive appended to a per-image bullet for the
|
|
46
|
+
* given mode. The text is intentionally explicit so the model knows exactly
|
|
47
|
+
* which aspects of the reference to take and which to derive from the rest of
|
|
48
|
+
* the prompt.
|
|
49
|
+
*
|
|
50
|
+
* `name` and `none` are minimal-intervention modes that return `null` — the
|
|
51
|
+
* caller emits NO trailing directive for them. `name` still includes the
|
|
52
|
+
* character name in the labeled subject (`Image A (shira)`); `none` suppresses
|
|
53
|
+
* the name as well (bare `Image A`) and excludes the character entirely from
|
|
54
|
+
* the top-level "Use these characters:" header when ALL of its mentions are
|
|
55
|
+
* `none`. Use these when the user wants the visual reference to speak for
|
|
56
|
+
* itself without textual bias.
|
|
57
|
+
*
|
|
58
|
+
* Calling sites: `resolveCharacterMentions` (shared/prompt-builder.ts) and the
|
|
59
|
+
* per-character canonical fallback (also in prompt-builder.ts), plus the video
|
|
60
|
+
* mention resolvers on frontend (execute-node.ts) and backend (payload-builder.ts)
|
|
61
|
+
* which mirror the same directive shape.
|
|
62
|
+
*/
|
|
63
|
+
export function usageModeDirective(mode: UsageMode): string | null {
|
|
64
|
+
switch (mode) {
|
|
65
|
+
case "identical":
|
|
66
|
+
return "The subject must remain exactly the same person — preserve 100% facial identity, bone structure, skin tone, proportions, and all unique features. Do not alter eyes, nose, mouth, or facial shape. Maintain natural skin texture, including pores and imperfections."
|
|
67
|
+
case "face":
|
|
68
|
+
return "Take ONLY the facial features and expression — preserve 100% facial identity, bone structure, skin tone, and all unique features. Do not alter eyes, nose, mouth, or facial shape. Maintain natural skin texture, including pores and imperfections. Adopt clothing, hair styling, and posture from the rest of the prompt."
|
|
69
|
+
case "face-pose":
|
|
70
|
+
return "Take the facial features AND body pose — preserve 100% facial identity, bone structure, skin tone, and all unique features. Do not alter eyes, nose, mouth, or facial shape. Maintain natural skin texture. Adopt clothing and styling from the rest of the prompt."
|
|
71
|
+
case "pose":
|
|
72
|
+
return "Take only the body pose and posture — do not copy facial identity, features, or skin. Adopt face, clothing, hair, and all other details from the rest of the prompt."
|
|
73
|
+
case "emotion":
|
|
74
|
+
return "Take only the emotional expression — preserve 100% facial identity and all unique features unchanged. Adopt face shape, clothing, body, and pose from the rest of the prompt; transfer only the emotional cue."
|
|
75
|
+
case "style":
|
|
76
|
+
return "Take only the visual style and tone (lighting, color grade, atmosphere). Do not copy the subject's identity, clothing, or pose."
|
|
77
|
+
case "name":
|
|
78
|
+
case "none":
|
|
79
|
+
return null
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Human-readable label for the dropdown / badge UI. */
|
|
84
|
+
export function usageModeLabel(mode: UsageMode): string {
|
|
85
|
+
switch (mode) {
|
|
86
|
+
case "identical": return "Identical"
|
|
87
|
+
case "face": return "Face only"
|
|
88
|
+
case "face-pose": return "Face + Pose"
|
|
89
|
+
case "pose": return "Pose only"
|
|
90
|
+
case "emotion": return "Emotion only"
|
|
91
|
+
case "style": return "Style only"
|
|
92
|
+
case "name": return "Name only"
|
|
93
|
+
case "none": return "None"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Whether the labeled subject for this mode should include the character name
|
|
99
|
+
* in parens (e.g. `Image A (shira)`) or stay bare (`Image A`). Only `none`
|
|
100
|
+
* returns false — every other mode (including `name`) keeps the name so the
|
|
101
|
+
* model can correlate the bullet with a named entity in the rest of the
|
|
102
|
+
* prompt. Used by `resolveCharacterMentions` to decide the subject form AND to
|
|
103
|
+
* filter the top-level "Use these characters:" header (a character whose
|
|
104
|
+
* mentions are ALL `none` is invisible textually — only the image is
|
|
105
|
+
* attached).
|
|
106
|
+
*/
|
|
107
|
+
export function usageModeIncludesName(mode: UsageMode): boolean {
|
|
108
|
+
return mode !== "none"
|
|
109
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for a character's `{name, url}[]` variant-asset buckets
|
|
3
|
+
* — the image/video variants that are expandable into connected references and
|
|
4
|
+
* therefore selectable in the reference picker AND `@`-mentionable in prompts.
|
|
5
|
+
*
|
|
6
|
+
* Historically the expansion list was hardcoded (6 copies across frontend
|
|
7
|
+
* `connected-references.ts` / `connected-refs-builder.ts` / `execute-node.ts` and
|
|
8
|
+
* backend `payload-builder.ts`), so when wardrobe (`outfitVariations`) and detail
|
|
9
|
+
* close-ups (`detailCloseups`) were added — same `{name,url}[]` shape, written by
|
|
10
|
+
* studio.nodaro.ai — they were silently dropped from the picker and `@` list.
|
|
11
|
+
* Drive every expansion site off this constant so a new bucket can't drift again.
|
|
12
|
+
*
|
|
13
|
+
* NOT included (different shapes, handled elsewhere):
|
|
14
|
+
* - `sheets` (`ReferenceSheet[]` — composited multi-panel boards)
|
|
15
|
+
* - `referenceVideosByVariant` (`Record<variant, url[]>` — per-emotion video refs)
|
|
16
|
+
*/
|
|
17
|
+
export const CHARACTER_VARIANT_ASSET_BUCKETS = [
|
|
18
|
+
"expressions",
|
|
19
|
+
"poses",
|
|
20
|
+
"motions",
|
|
21
|
+
"angles",
|
|
22
|
+
"bodyAngles",
|
|
23
|
+
"lightingVariations",
|
|
24
|
+
"outfitVariations",
|
|
25
|
+
"detailCloseups",
|
|
26
|
+
] as const
|
|
27
|
+
|
|
28
|
+
export type CharacterVariantAssetBucket = (typeof CHARACTER_VARIANT_ASSET_BUCKETS)[number]
|
|
29
|
+
|
|
30
|
+
export interface CharacterVariantAssetItem {
|
|
31
|
+
readonly name: string
|
|
32
|
+
readonly url: string
|
|
33
|
+
readonly description?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Build the `{ bucket: items[] }` map for every variant-asset bucket from a
|
|
38
|
+
* character node's data. Missing / non-array buckets coerce to `[]`. One helper
|
|
39
|
+
* for all six expansion sites so the bucket set is defined exactly once.
|
|
40
|
+
*/
|
|
41
|
+
export function characterVariantAssetArrays(
|
|
42
|
+
data: Record<string, unknown> | null | undefined,
|
|
43
|
+
): Record<CharacterVariantAssetBucket, readonly CharacterVariantAssetItem[]> {
|
|
44
|
+
const out = {} as Record<CharacterVariantAssetBucket, readonly CharacterVariantAssetItem[]>
|
|
45
|
+
for (const bucket of CHARACTER_VARIANT_ASSET_BUCKETS) {
|
|
46
|
+
const value = data?.[bucket]
|
|
47
|
+
out[bucket] = Array.isArray(value) ? (value as readonly CharacterVariantAssetItem[]) : []
|
|
48
|
+
}
|
|
49
|
+
return out
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Map a character's composite reference `sheets` (ReferenceSheet[] — a different
|
|
54
|
+
* shape, with a composite board `url` + `type`/`skin`) into mentionable
|
|
55
|
+
* `{name,url}` items so they too can be picked / `@`-mentioned. Each sheet → one
|
|
56
|
+
* item named `"<type> <skin>"` (e.g. "turnaround studio"). Drops url-less sheets.
|
|
57
|
+
*/
|
|
58
|
+
export function characterSheetRefItems(sheets: unknown): CharacterVariantAssetItem[] {
|
|
59
|
+
if (!Array.isArray(sheets)) return []
|
|
60
|
+
return sheets.flatMap((s) => {
|
|
61
|
+
const sheet = s as { url?: unknown; type?: unknown; skin?: unknown }
|
|
62
|
+
if (!sheet || typeof sheet.url !== "string" || !sheet.url) return []
|
|
63
|
+
const type = typeof sheet.type === "string" && sheet.type ? sheet.type : "sheet"
|
|
64
|
+
const skin = typeof sheet.skin === "string" && sheet.skin ? ` ${sheet.skin}` : ""
|
|
65
|
+
return [{ name: `${type}${skin}`, url: sheet.url }]
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Legacy `selected_asset_by_variant` keys that hold boards born before the
|
|
70
|
+
* `boards` column existed (studio.nodaro.ai's reserved namespace). */
|
|
71
|
+
const BOARD_SHIM_KEY = "studioBoard"
|
|
72
|
+
const BOARD_SHIM_PREFIX = "studioBoard:"
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Resolve a character's named reference boards from BOTH the `boards` column AND
|
|
76
|
+
* the LEGACY `selected_asset_by_variant` shim (`studioBoard` / `studioBoard:<name>`)
|
|
77
|
+
* — mirroring studio.nodaro.ai's read (entity-boards.ts), which merges both since
|
|
78
|
+
* pre-column boards still live in the shim. Column wins on duplicate name; the
|
|
79
|
+
* unnamed legacy board is named "board". Drops url-less entries.
|
|
80
|
+
*/
|
|
81
|
+
export function characterBoardItems(
|
|
82
|
+
data: Record<string, unknown> | null | undefined,
|
|
83
|
+
): CharacterVariantAssetItem[] {
|
|
84
|
+
const column: CharacterVariantAssetItem[] = Array.isArray(data?.boards)
|
|
85
|
+
? (data!.boards as unknown[]).flatMap((b) => {
|
|
86
|
+
const board = b as { name?: unknown; url?: unknown }
|
|
87
|
+
if (!board || typeof board.url !== "string" || !board.url) return []
|
|
88
|
+
return [{ name: typeof board.name === "string" && board.name ? board.name : "board", url: board.url }]
|
|
89
|
+
})
|
|
90
|
+
: []
|
|
91
|
+
|
|
92
|
+
const map = (data?.selectedAssetByVariant ?? {}) as Record<string, unknown>
|
|
93
|
+
const shim: CharacterVariantAssetItem[] = []
|
|
94
|
+
const legacy = map[BOARD_SHIM_KEY]
|
|
95
|
+
if (typeof legacy === "string" && legacy) shim.push({ name: "board", url: legacy })
|
|
96
|
+
for (const [k, v] of Object.entries(map)) {
|
|
97
|
+
if (k.startsWith(BOARD_SHIM_PREFIX) && typeof v === "string" && v) {
|
|
98
|
+
shim.push({ name: k.slice(BOARD_SHIM_PREFIX.length) || "board", url: v })
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const haveNames = new Set(column.map((b) => b.name.toLowerCase()))
|
|
103
|
+
return [...column, ...shim.filter((b) => !haveNames.has(b.name.toLowerCase()))]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The COMPLETE set of mentionable character asset arrays: the `{name,url}[]`
|
|
108
|
+
* variant buckets + derived `sheets` and `boards` buckets. This is what the
|
|
109
|
+
* connected-reference / `@`-mention expansion sites iterate, so every
|
|
110
|
+
* studio-produced asset (variants, composite sheets, AND boards — incl. legacy
|
|
111
|
+
* shim boards) surfaces in the reference picker and `@` list.
|
|
112
|
+
*/
|
|
113
|
+
export function characterMentionableAssetArrays(
|
|
114
|
+
data: Record<string, unknown> | null | undefined,
|
|
115
|
+
): Record<string, readonly CharacterVariantAssetItem[]> {
|
|
116
|
+
return {
|
|
117
|
+
...characterVariantAssetArrays(data),
|
|
118
|
+
sheets: characterSheetRefItems(data?.sheets),
|
|
119
|
+
boards: characterBoardItems(data),
|
|
120
|
+
}
|
|
121
|
+
}
|