@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,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Location `@-mention` parser. Mirrors `character-mention-slug.ts` shape but
|
|
3
|
+
* with two key differences:
|
|
4
|
+
*
|
|
5
|
+
* 1. **Bucketed variants:** the 3rd segment can be `bucket/variant` (slash-
|
|
6
|
+
* separated, e.g. `weather/rain` or `lighting/cinematic`) in addition
|
|
7
|
+
* to a bare usage-mode keyword. Locations carry their variants split
|
|
8
|
+
* across 5 buckets (timeOfDay / weather / seasons / angles / lighting)
|
|
9
|
+
* plus motion clips; the bucket prefix disambiguates which array to
|
|
10
|
+
* pull from when two buckets happen to share a variant name.
|
|
11
|
+
*
|
|
12
|
+
* 2. **Smaller usage-mode set:** locations support 4 modes ("identical",
|
|
13
|
+
* "style", "layout", "none") — a strict subset of the 8 character modes.
|
|
14
|
+
* The character modes "face" / "face-pose" / "pose" / "emotion" / "name"
|
|
15
|
+
* don't apply to scenes.
|
|
16
|
+
*
|
|
17
|
+
* The mention resolver (Phase 2 #2 follow-up) uses the parsed token to look
|
|
18
|
+
* up the variant URL by walking the upstream Location node's `node.data`:
|
|
19
|
+
* timeOfDay / weather / seasons / angles / lighting / atmosphereMotions
|
|
20
|
+
* arrays, each `{name, url}`. Slug match is case-insensitive via the same
|
|
21
|
+
* slugify helper used by character variants.
|
|
22
|
+
*
|
|
23
|
+
* Format reference (the parser accepts these shapes; everything else returns
|
|
24
|
+
* null and the token falls through to literal-text in the final prompt):
|
|
25
|
+
*
|
|
26
|
+
* @oldlibrary:1 — canonical, default mode
|
|
27
|
+
* @oldlibrary:1:weather/rain — rain variant from weather bucket
|
|
28
|
+
* @oldlibrary:1:layout — canonical, mode "layout"
|
|
29
|
+
* @oldlibrary:1:weather/rain:style — variant + mode override
|
|
30
|
+
* @oldlibrary:1:weather/rain:bogus — null (4th segment must be a known
|
|
31
|
+
* location usage mode)
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** Modes that apply to scenes/locations. Subset of character `USAGE_MODES`. */
|
|
35
|
+
export const LOCATION_USAGE_MODES = [
|
|
36
|
+
"identical",
|
|
37
|
+
"style",
|
|
38
|
+
"layout",
|
|
39
|
+
"none",
|
|
40
|
+
] as const
|
|
41
|
+
|
|
42
|
+
export type LocationUsageMode = (typeof LOCATION_USAGE_MODES)[number]
|
|
43
|
+
|
|
44
|
+
/** Default mode when neither the slug nor the location node specifies one. */
|
|
45
|
+
export const DEFAULT_LOCATION_USAGE_MODE: LocationUsageMode = "identical"
|
|
46
|
+
|
|
47
|
+
/** Type guard — narrows an arbitrary string to `LocationUsageMode`. */
|
|
48
|
+
export function isLocationUsageMode(s: string): s is LocationUsageMode {
|
|
49
|
+
return (LOCATION_USAGE_MODES as readonly string[]).includes(s)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Human-readable label for the location usage-mode dropdown / badge UI.
|
|
54
|
+
* Mirrors `usageModeLabel` for character modes but with location-specific
|
|
55
|
+
* phrasing — locations don't have face/pose semantics, so the labels speak
|
|
56
|
+
* about the scene as a whole (framing, layout, mood) instead of subject
|
|
57
|
+
* features. Used by the location pill's mode menu (slice 4) and the
|
|
58
|
+
* hierarchical drill in the `@`-autocomplete.
|
|
59
|
+
*
|
|
60
|
+
* Phrasing decisions:
|
|
61
|
+
* - "Match exactly" — strongest possible directive; the scene should
|
|
62
|
+
* reproduce the reference verbatim. Equivalent in role to character
|
|
63
|
+
* "Identical".
|
|
64
|
+
* - "Style / mood only" — atmospheric transfer (lighting, color grade,
|
|
65
|
+
* mood) without locking the framing. Maps to character "Style only".
|
|
66
|
+
* - "Layout / framing only" — keep the spatial composition (where things
|
|
67
|
+
* are, where the camera sits) without copying the textures or palette.
|
|
68
|
+
* No character-side equivalent.
|
|
69
|
+
* - "No textual bias" — visual reference only, no top-level header
|
|
70
|
+
* mention. Maps to character "None".
|
|
71
|
+
*/
|
|
72
|
+
export function locationUsageModeLabel(mode: LocationUsageMode): string {
|
|
73
|
+
switch (mode) {
|
|
74
|
+
case "identical": return "Match exactly"
|
|
75
|
+
case "style": return "Style / mood only"
|
|
76
|
+
case "layout": return "Layout / framing only"
|
|
77
|
+
case "none": return "No textual bias"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Slugify a location name for use in @-mention tokens. Same algorithm as
|
|
83
|
+
* `characterMentionSlug` — lowercase, strip non-alphanumeric, collapse runs.
|
|
84
|
+
* Kept as a separate export to make the call site's intent explicit and to
|
|
85
|
+
* allow future divergence (e.g., locations may need to preserve digits at
|
|
86
|
+
* the start, characters may not).
|
|
87
|
+
*/
|
|
88
|
+
export function locationMentionSlug(name: string): string {
|
|
89
|
+
return name
|
|
90
|
+
.toLowerCase()
|
|
91
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
92
|
+
.replace(/-+/g, "-")
|
|
93
|
+
.replace(/^-|-$/g, "")
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface LocationMentionTokenInfo {
|
|
97
|
+
readonly token: string
|
|
98
|
+
readonly locationSlug: string
|
|
99
|
+
readonly imageIndex: number
|
|
100
|
+
/** "weather", "lighting", etc. — null when the mention targets the canonical
|
|
101
|
+
* main image. */
|
|
102
|
+
readonly bucket: string | null
|
|
103
|
+
/** Variant name within the bucket (e.g. "rain"). null for canonical. */
|
|
104
|
+
readonly variant: string | null
|
|
105
|
+
/** Per-mention mode override; null falls back to the location node's default
|
|
106
|
+
* (or the global "identical" default). */
|
|
107
|
+
readonly usageMode: LocationUsageMode | null
|
|
108
|
+
/** Bare-slug ROLE (Unified Reference Roles, Phase D + F2) — any slash-less,
|
|
109
|
+
* non-mode 3rd segment (`background`, `empty-background`, `as-is`, … or a
|
|
110
|
+
* CUSTOM slug), stored verbatim in slug form. Absent (undefined) for canonical /
|
|
111
|
+
* bucket-variant / mode tokens. The hybrid resolver + role pill map it back to
|
|
112
|
+
* the phrase key via `normalizeRoleSlug` (custom slugs pass through). Optional so
|
|
113
|
+
* canonical/variant/mode tokens stay shape-identical to the pre-Phase-D parser
|
|
114
|
+
* output. */
|
|
115
|
+
readonly role?: string
|
|
116
|
+
/**
|
|
117
|
+
* Additive per-mention identity-lock sentinel (Unified Reference Roles,
|
|
118
|
+
* Task 4 + F4). Tri-state: `true` (`~lock`, force ON) | `false` (`~nolock`,
|
|
119
|
+
* force OFF — suppresses a ref-level `identityLock.enabled = true`) |
|
|
120
|
+
* ABSENT/undefined (neither — inherit the ref default; byte-identical to the
|
|
121
|
+
* pre-Task-4 shape). Only ACTED UPON by the HYBRID resolver; the LEGACY
|
|
122
|
+
* resolver ignores it, so both sentinels are inert on the legacy path.
|
|
123
|
+
*/
|
|
124
|
+
readonly lock?: boolean
|
|
125
|
+
/** Byte offset into the source prompt — used to splice the token out at
|
|
126
|
+
* resolve time. */
|
|
127
|
+
readonly offset: number
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Parse a single `@<location>:<index>(:<bucket>/<variant>|<mode>)?(:<mode>)?`
|
|
132
|
+
* token. Returns null when the token doesn't match any supported shape
|
|
133
|
+
* (caller falls back to literal text).
|
|
134
|
+
*
|
|
135
|
+
* Disambiguation at the 3rd segment: presence of `/` indicates a
|
|
136
|
+
* bucket/variant pair; absence indicates a bare mode keyword. The 4th
|
|
137
|
+
* segment is always a mode override (and must be a valid `LocationUsageMode`).
|
|
138
|
+
*/
|
|
139
|
+
export function parseLocationMentionToken(
|
|
140
|
+
text: string,
|
|
141
|
+
): {
|
|
142
|
+
locationSlug: string
|
|
143
|
+
imageIndex: number
|
|
144
|
+
bucket: string | null
|
|
145
|
+
variant: string | null
|
|
146
|
+
usageMode: LocationUsageMode | null
|
|
147
|
+
/** Bare-slug role (slug form). Present for ANY bare, non-mode 3rd segment
|
|
148
|
+
* (preset OR custom, F2); OMITTED (undefined) for every other shape so
|
|
149
|
+
* canonical/variant/mode results stay byte-identical to the pre-Phase-D parser. */
|
|
150
|
+
role?: string
|
|
151
|
+
/** Additive per-mention identity-lock sentinel (Task 4 + F4). Tri-state:
|
|
152
|
+
* `true` (`~lock`) | `false` (`~nolock`) | OMITTED (neither — byte-identical). */
|
|
153
|
+
lock?: boolean
|
|
154
|
+
} | null {
|
|
155
|
+
if (!text.startsWith("@")) return null
|
|
156
|
+
let rest = text.slice(1)
|
|
157
|
+
if (rest.length === 0 || !/^[a-z]/.test(rest)) return null
|
|
158
|
+
|
|
159
|
+
// Additive `~lock` / `~nolock` sentinels (Task 4 + F4). Strip a trailing
|
|
160
|
+
// `~nolock` (force lock OFF) or `~lock` (force lock ON) BEFORE splitting so
|
|
161
|
+
// the segment grammar stays byte-identical (a `~` never appears inside a
|
|
162
|
+
// bucket/variant/mode/role segment). Check `~nolock` FIRST. A token with
|
|
163
|
+
// NEITHER sentinel is untouched and gains NO `lock` key (byte-identical).
|
|
164
|
+
let lockField: { lock?: boolean } = {}
|
|
165
|
+
if (rest.endsWith("~nolock")) {
|
|
166
|
+
rest = rest.slice(0, -"~nolock".length)
|
|
167
|
+
lockField = { lock: false }
|
|
168
|
+
} else if (rest.endsWith("~lock")) {
|
|
169
|
+
rest = rest.slice(0, -"~lock".length)
|
|
170
|
+
lockField = { lock: true }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const parts = rest.split(":")
|
|
174
|
+
if (parts.length < 2 || parts.length > 4) return null
|
|
175
|
+
|
|
176
|
+
const [locationSlug, indexStr, third, fourth] = parts
|
|
177
|
+
if (!/^[a-z][a-z0-9-]*$/.test(locationSlug)) return null
|
|
178
|
+
if (!/^\d+$/.test(indexStr)) return null
|
|
179
|
+
const imageIndex = parseInt(indexStr, 10)
|
|
180
|
+
if (!Number.isInteger(imageIndex) || imageIndex < 1) return null
|
|
181
|
+
|
|
182
|
+
// 2-part: @oldlibrary:1 — canonical, default mode.
|
|
183
|
+
if (parts.length === 2) {
|
|
184
|
+
return {
|
|
185
|
+
locationSlug,
|
|
186
|
+
imageIndex,
|
|
187
|
+
bucket: null,
|
|
188
|
+
variant: null,
|
|
189
|
+
usageMode: null,
|
|
190
|
+
...lockField,
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 3-part: @oldlibrary:1:X — X is either bucket/variant or a mode keyword.
|
|
195
|
+
if (parts.length === 3) {
|
|
196
|
+
if (third.includes("/")) {
|
|
197
|
+
const slashAt = third.indexOf("/")
|
|
198
|
+
const bucket = third.slice(0, slashAt)
|
|
199
|
+
const variant = third.slice(slashAt + 1)
|
|
200
|
+
if (!/^[a-z][a-z0-9-]*$/.test(bucket)) return null
|
|
201
|
+
if (!/^[a-z][a-z0-9-]*$/.test(variant)) return null
|
|
202
|
+
return { locationSlug, imageIndex, bucket, variant, usageMode: null, ...lockField }
|
|
203
|
+
}
|
|
204
|
+
if (!/^[a-z][a-z0-9-]*$/.test(third)) return null
|
|
205
|
+
if (isLocationUsageMode(third)) {
|
|
206
|
+
return {
|
|
207
|
+
locationSlug,
|
|
208
|
+
imageIndex,
|
|
209
|
+
bucket: null,
|
|
210
|
+
variant: null,
|
|
211
|
+
usageMode: third,
|
|
212
|
+
...lockField,
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Additive (Unified Reference Roles, Phase D + F2 follow-up): a bare, non-
|
|
216
|
+
// mode 3rd segment parses as a ROLE, stored verbatim in slug form
|
|
217
|
+
// (`background`, `empty-background`, `as-is`, …, or a user's CUSTOM slug).
|
|
218
|
+
// The F2 follow-up REMOVED the preset membership gate so custom location
|
|
219
|
+
// roles behave like custom character roles — any valid slug (guaranteed
|
|
220
|
+
// `[a-z][a-z0-9-]*` by the regex above, and not a `LocationUsageMode`, which
|
|
221
|
+
// is caught first) is accepted. Legacy stays byte-identical NOT via this
|
|
222
|
+
// parser but via the resolver's `if (t.role) continue` guard, which skips
|
|
223
|
+
// ANY role token (preset or custom) → literal text with no bullet / phrase.
|
|
224
|
+
// The slug round-trips through the pill's `renderText`; the hybrid resolver /
|
|
225
|
+
// pill map it to a phrase via `normalizeRoleSlug` (custom slugs pass through
|
|
226
|
+
// unchanged). (`layout`/`style` never reach this branch — usage modes above.)
|
|
227
|
+
return {
|
|
228
|
+
locationSlug,
|
|
229
|
+
imageIndex,
|
|
230
|
+
bucket: null,
|
|
231
|
+
variant: null,
|
|
232
|
+
usageMode: null,
|
|
233
|
+
role: third,
|
|
234
|
+
...lockField,
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 4-part: @oldlibrary:1:bucket/variant:mode.
|
|
239
|
+
if (parts.length === 4) {
|
|
240
|
+
if (!third.includes("/")) return null
|
|
241
|
+
const slashAt = third.indexOf("/")
|
|
242
|
+
const bucket = third.slice(0, slashAt)
|
|
243
|
+
const variant = third.slice(slashAt + 1)
|
|
244
|
+
if (!/^[a-z][a-z0-9-]*$/.test(bucket)) return null
|
|
245
|
+
if (!/^[a-z][a-z0-9-]*$/.test(variant)) return null
|
|
246
|
+
if (!isLocationUsageMode(fourth)) return null
|
|
247
|
+
return { locationSlug, imageIndex, bucket, variant, usageMode: fourth, ...lockField }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return null
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Find all location @-mentions in a prompt that match a known location slug.
|
|
255
|
+
*
|
|
256
|
+
* The regex captures every `@<slug>:<index>(:<segment>)?(:<segment>)?` shape
|
|
257
|
+
* where each segment is either `[a-z][a-z0-9-]*` (the character format —
|
|
258
|
+
* shared with characters) or `[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*` (the
|
|
259
|
+
* bucket/variant format — location-only). `knownLocationSlugs` filters to
|
|
260
|
+
* only the user's wired locations so `@kira:1:smile` doesn't accidentally
|
|
261
|
+
* trip the location parser.
|
|
262
|
+
*/
|
|
263
|
+
export function findLocationMentionTokens(
|
|
264
|
+
prompt: string,
|
|
265
|
+
knownLocationSlugs: readonly string[],
|
|
266
|
+
): LocationMentionTokenInfo[] {
|
|
267
|
+
const tokens: LocationMentionTokenInfo[] = []
|
|
268
|
+
// Each optional segment is either a plain slug (`[a-z][a-z0-9-]*`) OR a
|
|
269
|
+
// bucket/variant pair (`[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*`). The two are
|
|
270
|
+
// disjoint at the regex level — bucket/variant requires the literal `/`.
|
|
271
|
+
const segment = "(?:[a-z][a-z0-9-]*\\/[a-z][a-z0-9-]*|[a-z][a-z0-9-]*)"
|
|
272
|
+
// The optional trailing `(?:~(?:no)?lock)?` absorbs the additive Task-4/F4
|
|
273
|
+
// identity-lock sentinel (`~lock` force-on OR `~nolock` force-off) INTO the
|
|
274
|
+
// captured token (spliced out at resolve time); optional, so a lock-less token
|
|
275
|
+
// matches byte-identically to before. `(?![a-z0-9-])` keeps `~locked` /
|
|
276
|
+
// `~nolockx` literal.
|
|
277
|
+
const regex = new RegExp(
|
|
278
|
+
`(?:^|[^a-zA-Z0-9])(@[a-z][a-z0-9-]*:\\d+(?::${segment})?(?::${segment})?(?:~(?:no)?lock(?![a-z0-9-]))?)`,
|
|
279
|
+
"g",
|
|
280
|
+
)
|
|
281
|
+
const knownSet = new Set(knownLocationSlugs)
|
|
282
|
+
for (const match of prompt.matchAll(regex)) {
|
|
283
|
+
const token = match[1]
|
|
284
|
+
const offset = (match.index ?? 0) + (match[0].length - token.length)
|
|
285
|
+
const parsed = parseLocationMentionToken(token)
|
|
286
|
+
if (parsed && knownSet.has(parsed.locationSlug)) {
|
|
287
|
+
tokens.push({ token, ...parsed, offset })
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return tokens
|
|
291
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter from the Location Studio's backend preset strings to canonical
|
|
3
|
+
* picker-catalog entries.
|
|
4
|
+
*
|
|
5
|
+
* The Location Studio's tab presets ship 46 backend-stable variant strings
|
|
6
|
+
* (e.g. "wide", "golden hour", "drone fly-over"). These strings are what the
|
|
7
|
+
* frontend sends to `POST /v1/generate-location-asset` and what backend Zod
|
|
8
|
+
* validates against the `VARIANTS` map in
|
|
9
|
+
* `backend/src/routes/generate-location-asset.ts`. They MUST stay stable.
|
|
10
|
+
*
|
|
11
|
+
* For display, however, we want translated labels that match the rest of
|
|
12
|
+
* the app. Instead of maintaining a parallel `location-variants` i18n
|
|
13
|
+
* catalog (the duplication this file replaces), the Studio looks up each
|
|
14
|
+
* preset against an existing canonical catalog — framing, lighting,
|
|
15
|
+
* atmosphere, camera-motions, seasons — and uses the existing localized
|
|
16
|
+
* label.
|
|
17
|
+
*
|
|
18
|
+
* Adding a new preset to a Location Studio tab: pick the closest entry in
|
|
19
|
+
* the relevant canonical catalog and add a mapping here. If the closest
|
|
20
|
+
* match is wrong, ADD a new entry to the canonical catalog (so the
|
|
21
|
+
* camera-motion picker / framing picker / etc. all benefit), then map.
|
|
22
|
+
*/
|
|
23
|
+
import type { I18nCatalogId } from "./i18n/types.js"
|
|
24
|
+
|
|
25
|
+
export interface LocationCatalogRef {
|
|
26
|
+
readonly catalogId: I18nCatalogId
|
|
27
|
+
readonly entryId: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Backend variant string → canonical catalog reference.
|
|
32
|
+
*
|
|
33
|
+
* Keys MUST match the strings in `VARIANTS` in
|
|
34
|
+
* `backend/src/routes/generate-location-asset.ts` AND the preset arrays in
|
|
35
|
+
* the Location Studio tab wrappers
|
|
36
|
+
* (`frontend/src/components/editor/location-studio/{time-of-day,weather,seasons,angles,lighting}-tab.tsx`)
|
|
37
|
+
* AND `motion-tab.tsx`'s MOTION_PRESETS.
|
|
38
|
+
*/
|
|
39
|
+
export const LOCATION_PRESET_TO_CATALOG: Readonly<Record<string, LocationCatalogRef>> = {
|
|
40
|
+
// ── timeOfDay (9) → lighting time-of-day category ────────────────────
|
|
41
|
+
"dawn": { catalogId: "lighting", entryId: "dawn" },
|
|
42
|
+
"morning": { catalogId: "lighting", entryId: "morning" },
|
|
43
|
+
"noon": { catalogId: "lighting", entryId: "noon" },
|
|
44
|
+
"afternoon": { catalogId: "lighting", entryId: "afternoon" },
|
|
45
|
+
"golden hour": { catalogId: "lighting", entryId: "golden-hour" },
|
|
46
|
+
"dusk": { catalogId: "lighting", entryId: "dusk" },
|
|
47
|
+
"blue hour": { catalogId: "lighting", entryId: "blue-hour" },
|
|
48
|
+
"night": { catalogId: "lighting", entryId: "night" },
|
|
49
|
+
"midnight": { catalogId: "lighting", entryId: "midnight" },
|
|
50
|
+
|
|
51
|
+
// ── weather (9) → atmosphere ─────────────────────────────────────────
|
|
52
|
+
"clear": { catalogId: "atmosphere", entryId: "clear" },
|
|
53
|
+
"cloudy": { catalogId: "atmosphere", entryId: "cloudy" },
|
|
54
|
+
"light rain": { catalogId: "atmosphere", entryId: "light-rain" },
|
|
55
|
+
"heavy rain": { catalogId: "atmosphere", entryId: "heavy-rain" },
|
|
56
|
+
"storm": { catalogId: "atmosphere", entryId: "storm" },
|
|
57
|
+
"snow": { catalogId: "atmosphere", entryId: "snow" },
|
|
58
|
+
"blizzard": { catalogId: "atmosphere", entryId: "blizzard" },
|
|
59
|
+
"fog": { catalogId: "atmosphere", entryId: "fog" },
|
|
60
|
+
"mist": { catalogId: "atmosphere", entryId: "mist" },
|
|
61
|
+
|
|
62
|
+
// ── seasons (4) → seasons (this catalog only exists to serve this tab) ─
|
|
63
|
+
"spring": { catalogId: "seasons", entryId: "spring" },
|
|
64
|
+
"summer": { catalogId: "seasons", entryId: "summer" },
|
|
65
|
+
"autumn": { catalogId: "seasons", entryId: "autumn" },
|
|
66
|
+
"winter": { catalogId: "seasons", entryId: "winter" },
|
|
67
|
+
|
|
68
|
+
// ── angles (8) → framing ────────────────────────────────────────────
|
|
69
|
+
"wide": { catalogId: "framing", entryId: "wide-shot" },
|
|
70
|
+
"medium": { catalogId: "framing", entryId: "medium-shot" },
|
|
71
|
+
"closeup": { catalogId: "framing", entryId: "close-up" },
|
|
72
|
+
// `framing` has `birds-eye` and `overhead` but no plain `aerial`. The
|
|
73
|
+
// semantic intent ("view from above") is `birds-eye`, so we route there.
|
|
74
|
+
"aerial": { catalogId: "framing", entryId: "birds-eye" },
|
|
75
|
+
"low-angle": { catalogId: "framing", entryId: "low-angle" },
|
|
76
|
+
"eye-level": { catalogId: "framing", entryId: "eye-level" },
|
|
77
|
+
"bird's-eye": { catalogId: "framing", entryId: "birds-eye" },
|
|
78
|
+
"dutch tilt": { catalogId: "framing", entryId: "dutch-angle" },
|
|
79
|
+
|
|
80
|
+
// ── lighting (8) → lighting non-time-of-day categories ───────────────
|
|
81
|
+
// (`"blue hour"` is shared with timeOfDay above — same canonical entry.)
|
|
82
|
+
"soft natural": { catalogId: "lighting", entryId: "natural" },
|
|
83
|
+
"harsh sunlight": { catalogId: "lighting", entryId: "harsh-midday" },
|
|
84
|
+
"golden": { catalogId: "lighting", entryId: "golden-hour" },
|
|
85
|
+
"neon": { catalogId: "lighting", entryId: "neon-night" },
|
|
86
|
+
"candlelit": { catalogId: "lighting", entryId: "candlelight" },
|
|
87
|
+
"cinematic": { catalogId: "lighting", entryId: "three-point" },
|
|
88
|
+
"dramatic chiaroscuro": { catalogId: "lighting", entryId: "chiaroscuro" },
|
|
89
|
+
|
|
90
|
+
// ── motion (8) → camera-motions ──────────────────────────────────────
|
|
91
|
+
// The "slow" / "gentle" qualifiers are dropped at lookup time — the
|
|
92
|
+
// chip displays the canonical motion name ("Dolly-In", "Pan Left") with
|
|
93
|
+
// the user's locale applied.
|
|
94
|
+
"slow dolly-in": { catalogId: "camera-motions", entryId: "dolly-in" },
|
|
95
|
+
"slow pan-left": { catalogId: "camera-motions", entryId: "pan-left" },
|
|
96
|
+
"slow pan-right": { catalogId: "camera-motions", entryId: "pan-right" },
|
|
97
|
+
"push up": { catalogId: "camera-motions", entryId: "pedestal-up" },
|
|
98
|
+
"drone fly-over": { catalogId: "camera-motions", entryId: "fly-over" },
|
|
99
|
+
"gentle drift": { catalogId: "camera-motions", entryId: "gentle-drift" },
|
|
100
|
+
"parallax": { catalogId: "camera-motions", entryId: "parallax" },
|
|
101
|
+
"static atmospheric": { catalogId: "camera-motions", entryId: "static" },
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Resolve a backend preset string to its canonical catalog reference, or
|
|
106
|
+
* undefined if the preset is unknown (caller falls back to the raw
|
|
107
|
+
* preset string as the display label).
|
|
108
|
+
*/
|
|
109
|
+
export function resolveLocationPresetCatalog(preset: string): LocationCatalogRef | undefined {
|
|
110
|
+
return LOCATION_PRESET_TO_CATALOG[preset]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Each Location Studio tab pulls all its localized labels from a single
|
|
115
|
+
* canonical catalog (the tab's bucket → catalog mapping below). This lets
|
|
116
|
+
* the tab call `useLocalizedCatalog(LOCATION_BUCKET_TO_CATALOG_ID[bucket])`
|
|
117
|
+
* once and resolve every preset chip through the same catalog.
|
|
118
|
+
*
|
|
119
|
+
* Atmosphere motion (motion-tab.tsx) uses `camera-motions` directly — it's
|
|
120
|
+
* a single-bucket tab so the mapping isn't needed there.
|
|
121
|
+
*/
|
|
122
|
+
export const LOCATION_BUCKET_TO_CATALOG_ID = {
|
|
123
|
+
timeOfDay: "lighting",
|
|
124
|
+
weather: "atmosphere",
|
|
125
|
+
seasons: "seasons",
|
|
126
|
+
angles: "framing",
|
|
127
|
+
lighting: "lighting",
|
|
128
|
+
} as const satisfies Record<string, I18nCatalogId>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentinel value persisted by the "None" delimiter option in the UI. Distinct
|
|
3
|
+
* from `undefined` (which means "default to newline") so we can tell the
|
|
4
|
+
* difference between "user actively chose not to split" and "field never set".
|
|
5
|
+
*/
|
|
6
|
+
export const NO_SPLIT_DELIMITER = "__none__" as const
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Split text using a loop node's column delimiter.
|
|
10
|
+
*
|
|
11
|
+
* Uses the first text-type column's `splitDelimiter` to determine
|
|
12
|
+
* how to split upstream text. Falls back to newline when no delimiter
|
|
13
|
+
* is configured. The "first text column wins" heuristic is intentional:
|
|
14
|
+
* connected mode pipes a single text stream into the loop, so we use
|
|
15
|
+
* the primary text column's delimiter as the split strategy.
|
|
16
|
+
*
|
|
17
|
+
* `splitDelimiter === NO_SPLIT_DELIMITER` ("__none__") returns the input as
|
|
18
|
+
* a single-item array — the user explicitly opted out of splitting.
|
|
19
|
+
*/
|
|
20
|
+
export function splitByLoopDelimiter(
|
|
21
|
+
text: string,
|
|
22
|
+
columns: ReadonlyArray<{ type?: string; splitDelimiter?: string }> | undefined,
|
|
23
|
+
): string[] {
|
|
24
|
+
const firstTextCol = (columns ?? []).find((c) => (c.type ?? "text") === "text")
|
|
25
|
+
const raw = firstTextCol?.splitDelimiter
|
|
26
|
+
if (raw === NO_SPLIT_DELIMITER) {
|
|
27
|
+
const trimmed = text.trim()
|
|
28
|
+
return trimmed.length > 0 ? [trimmed] : []
|
|
29
|
+
}
|
|
30
|
+
const delimiter = raw ?? "\n"
|
|
31
|
+
return text.split(delimiter).map((s) => s.trim()).filter((s) => s.length > 0)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Split pasted (or manually-triggered) text into rows, inserting below
|
|
36
|
+
* the source row. Returns the new rows array and truncation metadata.
|
|
37
|
+
*
|
|
38
|
+
* Pure function — callers handle persisting the result and showing toasts.
|
|
39
|
+
*/
|
|
40
|
+
export function spliceDelimitedRows(
|
|
41
|
+
rows: readonly (readonly string[])[],
|
|
42
|
+
rowIndex: number,
|
|
43
|
+
colIndex: number,
|
|
44
|
+
pastedText: string,
|
|
45
|
+
delimiter: string,
|
|
46
|
+
columnCount: number,
|
|
47
|
+
maxItems: number,
|
|
48
|
+
): { newRows: string[][]; truncated: boolean; totalProduced: number } {
|
|
49
|
+
const parts = pastedText.split(delimiter).map((s) => s.trim()).filter((s) => s.length > 0)
|
|
50
|
+
if (parts.length <= 1) {
|
|
51
|
+
// Nothing to split — return rows unchanged with first part (or original) in cell
|
|
52
|
+
const newRows = rows.map((r) => [...r])
|
|
53
|
+
if (parts.length === 1) newRows[rowIndex][colIndex] = parts[0]
|
|
54
|
+
return { newRows, truncated: false, totalProduced: newRows.length }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const newRows = rows.map((r) => [...r])
|
|
58
|
+
newRows[rowIndex][colIndex] = parts[0]
|
|
59
|
+
const insertRows = parts.slice(1).map((part) => {
|
|
60
|
+
const r = Array.from<string>({ length: columnCount }).fill("")
|
|
61
|
+
r[colIndex] = part
|
|
62
|
+
return r
|
|
63
|
+
})
|
|
64
|
+
newRows.splice(rowIndex + 1, 0, ...insertRows)
|
|
65
|
+
|
|
66
|
+
const truncated = newRows.length > maxItems
|
|
67
|
+
const totalProduced = newRows.length
|
|
68
|
+
return {
|
|
69
|
+
newRows: truncated ? newRows.slice(0, maxItems) : newRows,
|
|
70
|
+
truncated,
|
|
71
|
+
totalProduced,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Character LoRA routing decision + identifier constants.
|
|
3
|
+
*
|
|
4
|
+
* Shared between backend (orchestrator's payload-builder) and frontend
|
|
5
|
+
* (single-node Run's execute-node). MUST agree byte-for-byte — the backend
|
|
6
|
+
* stamps the credit identifier and worker payload off this decision, the
|
|
7
|
+
* frontend stamps the `_internalLora` body hint off it. Mismatches would
|
|
8
|
+
* silently degrade trained-character generations to ref injection.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Synthetic Replicate model id selected by the orchestrator (and the
|
|
13
|
+
* single-node Run pre-Zod swap) when a single trained `@character` mention
|
|
14
|
+
* is detected. Routes through `black-forest-labs/flux-dev-lora` with the
|
|
15
|
+
* character's trained version under `extraParams.lora_version`. Never
|
|
16
|
+
* appears in user-facing dropdowns.
|
|
17
|
+
*/
|
|
18
|
+
export const FLUX_LORA_CHARACTER_MODEL_ID = "flux-lora-character" as const
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* `jobs.job_type` discriminator + credit identifier for character LoRA
|
|
22
|
+
* training jobs. Used for `STATIC_CREDIT_COSTS`/`model_pricing` lookup,
|
|
23
|
+
* `creditGuard` reservation, refund flow, and webhook job lookup.
|
|
24
|
+
*/
|
|
25
|
+
export const CHARACTER_LORA_TRAINING_JOB_TYPE = "character-lora-training" as const
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The three LoRA fields read off `CharacterNodeData` and stamped onto every
|
|
29
|
+
* `ConnectedReference` produced from a wired character. Shared between the
|
|
30
|
+
* backend's `expandWiredCharacterRefs` and the frontend's
|
|
31
|
+
* `expandCharacterNodeIntoRefs` so both sides emit the same three fields
|
|
32
|
+
* without copy-paste drift.
|
|
33
|
+
*/
|
|
34
|
+
export interface CharacterLoraFields {
|
|
35
|
+
readonly loraReplicateVersion: string | null
|
|
36
|
+
readonly loraTriggerWord: string | null
|
|
37
|
+
readonly loraTrainingStatus: string | null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Extract the three LoRA fields from a character node's `data`, normalized
|
|
42
|
+
* to `null` for missing/undefined values so downstream type-checks (e.g.
|
|
43
|
+
* `selectLoraRoutingForMentions`'s `=== "succeeded"`) are deterministic.
|
|
44
|
+
*/
|
|
45
|
+
export function extractCharacterLoraFields(charData: {
|
|
46
|
+
loraReplicateVersion?: string | null
|
|
47
|
+
loraTriggerWord?: string | null
|
|
48
|
+
loraTrainingStatus?: string | null
|
|
49
|
+
}): CharacterLoraFields {
|
|
50
|
+
return {
|
|
51
|
+
loraReplicateVersion: charData.loraReplicateVersion ?? null,
|
|
52
|
+
loraTriggerWord: charData.loraTriggerWord ?? null,
|
|
53
|
+
loraTrainingStatus: charData.loraTrainingStatus ?? null,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface LoraRouting {
|
|
58
|
+
readonly characterSlug: string
|
|
59
|
+
readonly triggerWord: string
|
|
60
|
+
readonly loraVersion: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface LoraEligibleRef {
|
|
64
|
+
readonly characterSlug?: string
|
|
65
|
+
readonly loraReplicateVersion?: string | null
|
|
66
|
+
readonly loraTriggerWord?: string | null
|
|
67
|
+
readonly loraTrainingStatus?: string | null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Returns a LoraRouting iff EXACTLY ONE distinct character is mentioned AND
|
|
72
|
+
* that character has a successful LoRA. Returns null otherwise → caller
|
|
73
|
+
* falls back to ref injection. Multi-character LoRA composition is Phase 2.
|
|
74
|
+
*/
|
|
75
|
+
export function selectLoraRoutingForMentions(
|
|
76
|
+
refs: readonly LoraEligibleRef[],
|
|
77
|
+
): LoraRouting | null {
|
|
78
|
+
const distinct = new Set(refs.map((r) => r.characterSlug).filter(Boolean))
|
|
79
|
+
if (distinct.size !== 1) return null
|
|
80
|
+
const slug = [...distinct][0]!
|
|
81
|
+
const match = refs.find((r) => r.characterSlug === slug)
|
|
82
|
+
if (
|
|
83
|
+
!match ||
|
|
84
|
+
!match.loraReplicateVersion ||
|
|
85
|
+
!match.loraTriggerWord ||
|
|
86
|
+
match.loraTrainingStatus !== "succeeded"
|
|
87
|
+
) {
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
characterSlug: slug,
|
|
92
|
+
triggerWord: match.loraTriggerWord,
|
|
93
|
+
loraVersion: match.loraReplicateVersion,
|
|
94
|
+
}
|
|
95
|
+
}
|