@nodaro/prompts 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +105 -0
- package/README.md +27 -0
- package/dist/index.cjs +21240 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3599 -0
- package/dist/index.d.ts +3599 -0
- package/dist/index.js +20854 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
- package/src/__tests__/__snapshots__/prompt-builder-segments.test.ts.snap +65 -0
- package/src/__tests__/action-fx.test.ts +155 -0
- package/src/__tests__/apply-picker-json.test.ts +50 -0
- package/src/__tests__/assemble-image-input.test.ts +248 -0
- package/src/__tests__/assemble-suno-input.test.ts +283 -0
- package/src/__tests__/brand-tokens.test.ts +81 -0
- package/src/__tests__/build-image-prompt-element-injection.test.ts +118 -0
- package/src/__tests__/build-image-prompt-hybrid-format.test.ts +131 -0
- package/src/__tests__/build-image-prompt-mentions.test.ts +809 -0
- package/src/__tests__/build-image-prompt-reference-cap.test.ts +57 -0
- package/src/__tests__/build-image-prompt-reference-numbering.test.ts +237 -0
- package/src/__tests__/build-image-prompt-reference-order.test.ts +313 -0
- package/src/__tests__/camera-motions-from-connections.test.ts +71 -0
- package/src/__tests__/catalog-gapfill.test.ts +53 -0
- package/src/__tests__/character-convergence-image.test.ts +217 -0
- package/src/__tests__/character-default-role-image.test.ts +167 -0
- package/src/__tests__/character-default-role-video.test.ts +168 -0
- package/src/__tests__/character-default-role.test.ts +70 -0
- package/src/__tests__/character-fx.test.ts +200 -0
- package/src/__tests__/entity-prompts-location.test.ts +97 -0
- package/src/__tests__/entity-prompts.test.ts +240 -0
- package/src/__tests__/expand-extra-refs-role.test.ts +50 -0
- package/src/__tests__/factory-presets.test.ts +1045 -0
- package/src/__tests__/factory-snippets.test.ts +68 -0
- package/src/__tests__/framing-multi.test.ts +71 -0
- package/src/__tests__/framing-vantage.test.ts +39 -0
- package/src/__tests__/i18n-entry-completeness.test.ts +269 -0
- package/src/__tests__/identity-lock.test.ts +46 -0
- package/src/__tests__/instrumentation.test.ts +68 -0
- package/src/__tests__/lighting-multi.test.ts +68 -0
- package/src/__tests__/location-convergence-image.test.ts +124 -0
- package/src/__tests__/mention-lock-flag.test.ts +499 -0
- package/src/__tests__/multi-picker-spec.test.ts +71 -0
- package/src/__tests__/music-genre.test.ts +103 -0
- package/src/__tests__/music-mood.test.ts +87 -0
- package/src/__tests__/object-creature-convergence-image.test.ts +105 -0
- package/src/__tests__/parameter-prompt-hint.test.ts +270 -0
- package/src/__tests__/parameter-registry-sync.test.ts +243 -0
- package/src/__tests__/person-age.test.ts +80 -0
- package/src/__tests__/person-analyzer-invariants.test.ts +47 -0
- package/src/__tests__/person-body-axes.test.ts +67 -0
- package/src/__tests__/person-facial-geometry.test.ts +156 -0
- package/src/__tests__/person-regional-aesthetic.test.ts +161 -0
- package/src/__tests__/person-sections.test.ts +18 -0
- package/src/__tests__/picker-analyzer-registry.test.ts +108 -0
- package/src/__tests__/picker-catalogs-project.test.ts +85 -0
- package/src/__tests__/picker-catalogs.test.ts +53 -0
- package/src/__tests__/picker-limits.test.ts +20 -0
- package/src/__tests__/prompt-builder-segments.test.ts +183 -0
- package/src/__tests__/prompt-builder-structured-fields.test.ts +40 -0
- package/src/__tests__/prompt-builder.test.ts +1773 -0
- package/src/__tests__/prompt-wizard-categories.test.ts +31 -0
- package/src/__tests__/provider-prompt-doctrine.test.ts +49 -0
- package/src/__tests__/resolve-prompt-append.test.ts +58 -0
- package/src/__tests__/resolve-prompt.test.ts +44 -0
- package/src/__tests__/role-picker-shared.test.ts +208 -0
- package/src/__tests__/seedance-2-inputs.test.ts +173 -0
- package/src/__tests__/seedance-extend.test.ts +44 -0
- package/src/__tests__/sound-aggregator.test.ts +350 -0
- package/src/__tests__/style-presets.test.ts +34 -0
- package/src/__tests__/temporal-multi.test.ts +74 -0
- package/src/__tests__/transitions.test.ts +213 -0
- package/src/__tests__/video-reference-features.test.ts +41 -0
- package/src/__tests__/video-reference-leading-refs.test.ts +90 -0
- package/src/__tests__/video-reference-resolver.test.ts +233 -0
- package/src/__tests__/video-reference-roles.test.ts +96 -0
- package/src/__tests__/voice-character.test.ts +48 -0
- package/src/__tests__/voice-delivery.test.ts +41 -0
- package/src/__tests__/wardrobe.test.ts +25 -0
- package/src/action-fx.ts +255 -0
- package/src/aesthetic.ts +435 -0
- package/src/assemble-image-input.ts +236 -0
- package/src/assemble-suno-input.ts +147 -0
- package/src/atmosphere.ts +104 -0
- package/src/backdrop.ts +131 -0
- package/src/brand-tokens.ts +154 -0
- package/src/camera-format.ts +76 -0
- package/src/camera-motions.ts +615 -0
- package/src/character-fx.ts +274 -0
- package/src/color-look.ts +103 -0
- package/src/composition-effects.ts +67 -0
- package/src/entity-prompts.ts +231 -0
- package/src/era.ts +292 -0
- package/src/exposure-settings.ts +142 -0
- package/src/factory-presets/generate-image.ts +1644 -0
- package/src/factory-presets/generate-video.ts +1116 -0
- package/src/factory-presets/index.ts +46 -0
- package/src/factory-presets/lottie-overlay.ts +166 -0
- package/src/factory-presets/motion-graphics.ts +350 -0
- package/src/factory-presets/music.ts +734 -0
- package/src/factory-presets/sfx.ts +136 -0
- package/src/factory-presets/shared-image.ts +207 -0
- package/src/factory-presets/switchx.ts +65 -0
- package/src/factory-presets/text.ts +292 -0
- package/src/factory-presets/types.ts +51 -0
- package/src/factory-presets/video-edit.ts +136 -0
- package/src/factory-presets/voice.ts +172 -0
- package/src/factory-presets.ts +2 -0
- package/src/factory-snippets/catalog.ts +105 -0
- package/src/factory-snippets/index.ts +16 -0
- package/src/factory-snippets/types.ts +33 -0
- package/src/framing.ts +634 -0
- package/src/held-prop.ts +187 -0
- package/src/identity-lock.ts +213 -0
- package/src/index.ts +66 -0
- package/src/instrumentation.ts +337 -0
- package/src/lens.ts +59 -0
- package/src/lighting.ts +229 -0
- package/src/loop-subject.ts +276 -0
- package/src/materials.ts +184 -0
- package/src/mood.ts +186 -0
- package/src/music-genre.ts +662 -0
- package/src/music-mood.ts +137 -0
- package/src/object-asset-presets.ts +81 -0
- package/src/parameter-prompt-hint.ts +281 -0
- package/src/person.ts +1368 -0
- package/src/photo-genre.ts +151 -0
- package/src/photographer.ts +612 -0
- package/src/picker-analyzer-registry.ts +374 -0
- package/src/picker-catalogs.ts +858 -0
- package/src/pose.ts +246 -0
- package/src/post-process-effects.ts +94 -0
- package/src/prompt-builder-structured-fields.ts +116 -0
- package/src/prompt-builder.ts +2964 -0
- package/src/prompt-templates.ts +50 -0
- package/src/prompt-wizard-categories.ts +334 -0
- package/src/provider-prompt-doctrine.ts +85 -0
- package/src/render-quality.ts +89 -0
- package/src/resolve-prompt.ts +120 -0
- package/src/seedance-2-inputs.ts +100 -0
- package/src/setting.ts +130 -0
- package/src/sound-aggregator.ts +241 -0
- package/src/style-presets.ts +162 -0
- package/src/style.ts +99 -0
- package/src/styling.ts +585 -0
- package/src/temporal.ts +151 -0
- package/src/transitions.ts +333 -0
- package/src/video-reference-resolver.ts +823 -0
- package/src/voice-character.ts +237 -0
- package/src/voice-delivery.ts +142 -0
- package/src/wardrobe.ts +179 -0
package/src/held-prop.ts
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical catalog of Held Prop / hand-prop presets.
|
|
3
|
+
*
|
|
4
|
+
* Single-pick parameter node — user picks ONE prop the subject is
|
|
5
|
+
* actively holding or interacting with. Distinct from the Object node,
|
|
6
|
+
* which describes a *separate* object somewhere in the scene. A held
|
|
7
|
+
* prop is part of the subject's pose grammar — they're cradling it,
|
|
8
|
+
* raising it, smoking it, gripping it. Each promptHint therefore
|
|
9
|
+
* incorporates a small piece of pose / hand language so the prop reads
|
|
10
|
+
* as actively held rather than just "near."
|
|
11
|
+
*
|
|
12
|
+
* There is intentional vocabulary overlap with the Weapon node (katana,
|
|
13
|
+
* pistol) and with parts of the Material / Vehicle catalogs — Held Prop
|
|
14
|
+
* uses the held-in-hand grammar ("holding…", "cradling…"), while the
|
|
15
|
+
* Weapon node uses the descriptive scene grammar ("with a katana, …").
|
|
16
|
+
*
|
|
17
|
+
* Applies to both image and video consumers (the prop and pose carry
|
|
18
|
+
* over to motion). Not in STILL_IMAGE_EXCLUDE_TYPES.
|
|
19
|
+
*
|
|
20
|
+
* Shared between the picker UI, the standalone Held Prop parameter
|
|
21
|
+
* node, and the prompt-hint injection on both the frontend DAG executor
|
|
22
|
+
* and the backend orchestrator.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export type HeldPropCategory =
|
|
26
|
+
| "device"
|
|
27
|
+
| "drink"
|
|
28
|
+
| "smoking"
|
|
29
|
+
| "reading-writing"
|
|
30
|
+
| "bag-accessory"
|
|
31
|
+
| "floral-nature"
|
|
32
|
+
| "instrument"
|
|
33
|
+
| "companion"
|
|
34
|
+
| "occupational"
|
|
35
|
+
|
|
36
|
+
export interface HeldProp {
|
|
37
|
+
readonly id: string
|
|
38
|
+
readonly label: string
|
|
39
|
+
readonly category: HeldPropCategory
|
|
40
|
+
readonly description: string
|
|
41
|
+
readonly promptHint: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const HELD_PROPS: ReadonlyArray<HeldProp> = [
|
|
45
|
+
// -------------------- Devices / Phones --------------------
|
|
46
|
+
{ id: "smartphone", label: "Smartphone", category: "device", description: "Modern phone in hand", promptHint: "holding a modern smartphone in one hand, screen lightly catching the ambient light, fingers wrapped naturally around the device" },
|
|
47
|
+
{ id: "smartphone-raised", label: "Phone Raised", category: "device", description: "Phone raised mid-photo", promptHint: "holding a smartphone raised at arm's length toward the camera as if mid-photo, both the phone and the gesture clearly visible" },
|
|
48
|
+
{ id: "polaroid-camera", label: "Polaroid Camera", category: "device", description: "Vintage instant camera", promptHint: "cradling a vintage Polaroid instant camera in both hands at chest height, the boxy white body and rainbow stripe clearly visible" },
|
|
49
|
+
{ id: "vintage-camera", label: "Vintage Camera", category: "device", description: "Old film camera with strap", promptHint: "holding a vintage 35mm film camera lifted toward the face with a leather strap looped around the neck, fingers poised over the shutter" },
|
|
50
|
+
{ id: "dslr-camera", label: "DSLR Camera", category: "device", description: "Modern DSLR / mirrorless camera", promptHint: "holding a modern DSLR camera up to the eye with both hands wrapped around the body and lens, mid-composition" },
|
|
51
|
+
{ id: "video-camera", label: "Video Camera", category: "device", description: "Shoulder-mounted video camera", promptHint: "shouldering a professional video camera with one hand on the side grip and the other steadying the body, eye to the viewfinder" },
|
|
52
|
+
{ id: "microphone", label: "Microphone", category: "device", description: "Handheld vocal microphone", promptHint: "gripping a handheld microphone close to the lips with one hand, the cable looping down from the bottom" },
|
|
53
|
+
{ id: "megaphone", label: "Megaphone", category: "device", description: "Bullhorn / megaphone", promptHint: "holding a bullhorn megaphone raised toward the mouth with the trigger pressed, the wide cone aimed forward" },
|
|
54
|
+
{ id: "smartwatch", label: "Smartwatch", category: "device", description: "Wrist raised to check watch", promptHint: "raising one wrist into frame with a sleek smartwatch face glowing on it, the other hand lightly steadying the band" },
|
|
55
|
+
|
|
56
|
+
// -------------------- Drinks --------------------
|
|
57
|
+
{ id: "coffee-cup", label: "Coffee Cup", category: "drink", description: "Ceramic coffee cup", promptHint: "cradling a warm ceramic coffee cup in both hands at chest height, soft steam rising from the rim" },
|
|
58
|
+
{ id: "takeaway-coffee", label: "Takeaway Coffee", category: "drink", description: "Paper takeaway coffee cup", promptHint: "holding a paper takeaway coffee cup with a sleeve and lid in one hand, steam drifting from the small opening" },
|
|
59
|
+
{ id: "wine-glass", label: "Wine Glass", category: "drink", description: "Stemmed glass of red wine", promptHint: "holding a stemmed wine glass by the bowl, deep red wine catching the warm ambient light" },
|
|
60
|
+
{ id: "champagne-flute", label: "Champagne Flute", category: "drink", description: "Tall champagne flute", promptHint: "holding a tall champagne flute by the stem, bubbles rising in the pale gold liquid" },
|
|
61
|
+
{ id: "martini-glass", label: "Martini Glass", category: "drink", description: "Classic martini glass", promptHint: "holding a classic conical martini glass by the stem at chest height, a single olive on a pick resting in the clear liquor" },
|
|
62
|
+
{ id: "cocktail-glass", label: "Cocktail Glass", category: "drink", description: "Short glass with cocktail", promptHint: "holding a short rocks glass with an amber cocktail and a single ice sphere, two fingers pressed against the chilled side" },
|
|
63
|
+
{ id: "beer-bottle", label: "Beer Bottle", category: "drink", description: "Brown bottle of beer", promptHint: "holding a brown glass beer bottle loosely by the neck, condensation beading on the surface" },
|
|
64
|
+
{ id: "water-bottle", label: "Water Bottle", category: "drink", description: "Reusable water bottle", promptHint: "holding a tall reusable water bottle in one hand at hip height, condensation beading on its sides" },
|
|
65
|
+
|
|
66
|
+
// -------------------- Smoking --------------------
|
|
67
|
+
{ id: "cigarette", label: "Cigarette", category: "smoking", description: "Lit cigarette between fingers", promptHint: "holding a lit cigarette pinched between two fingers raised near the face, a thin curl of smoke drifting upward" },
|
|
68
|
+
{ id: "cigar", label: "Cigar", category: "smoking", description: "Thick lit cigar", promptHint: "holding a thick lit cigar between two fingers, a slow wisp of smoke rising and an ember glowing at the tip" },
|
|
69
|
+
{ id: "vape-pen", label: "Vape Pen", category: "smoking", description: "Slim vape pen", promptHint: "holding a slim vape pen between two fingers, a soft puff of vapor curling out from a half-parted mouth" },
|
|
70
|
+
{ id: "joint", label: "Joint", category: "smoking", description: "Hand-rolled joint", promptHint: "holding a hand-rolled joint pinched between thumb and forefinger, a thin trail of smoke rising into the frame" },
|
|
71
|
+
{ id: "lighter", label: "Lighter", category: "smoking", description: "Chrome lighter with thumb on flame", promptHint: "holding a polished chrome lighter cupped in one hand with the thumb pressed on the wheel, a small warm flame catching across the fingers" },
|
|
72
|
+
|
|
73
|
+
// -------------------- Reading / Writing --------------------
|
|
74
|
+
{ id: "book", label: "Book", category: "reading-writing", description: "Open hardback book", promptHint: "holding an open hardback book in both hands at chest height, eyes drifting down toward the page" },
|
|
75
|
+
{ id: "magazine", label: "Magazine", category: "reading-writing", description: "Glossy folded magazine", promptHint: "holding a glossy magazine folded back on itself in one hand, the cover partly visible from the side" },
|
|
76
|
+
{ id: "newspaper", label: "Newspaper", category: "reading-writing", description: "Folded broadsheet newspaper", promptHint: "holding a folded broadsheet newspaper open between both hands at chest height, masthead lightly visible" },
|
|
77
|
+
{ id: "notebook", label: "Notebook", category: "reading-writing", description: "Open lined notebook", promptHint: "holding an open lined notebook in one hand at chest height with a pen poised above the page in the other" },
|
|
78
|
+
{ id: "pen", label: "Pen", category: "reading-writing", description: "Pen poised mid-write", promptHint: "holding a slender pen between thumb and fingers, the tip poised over an unseen surface as if mid-thought" },
|
|
79
|
+
{ id: "marker", label: "Marker", category: "reading-writing", description: "Thick marker mid-stroke", promptHint: "holding a thick permanent marker in one hand, cap clenched in the teeth and the tip poised mid-stroke" },
|
|
80
|
+
{ id: "paintbrush", label: "Paintbrush", category: "reading-writing", description: "Loaded paintbrush", promptHint: "holding a loaded paintbrush between thumb and fingers, bristles glistening with wet color and the wrist softly turned" },
|
|
81
|
+
{ id: "chalk", label: "Chalk", category: "reading-writing", description: "White stick of chalk", promptHint: "holding a short stick of white chalk between thumb and forefinger, faint chalk dust drifting from the tip as if mid-stroke" },
|
|
82
|
+
|
|
83
|
+
// -------------------- Bags / Accessories --------------------
|
|
84
|
+
{ id: "handbag", label: "Handbag", category: "bag-accessory", description: "Designer handbag", promptHint: "carrying a structured designer handbag by the top handles in one hand at hip height, the bag positioned cleanly in frame" },
|
|
85
|
+
{ id: "tote-bag", label: "Tote Bag", category: "bag-accessory", description: "Soft canvas tote", promptHint: "carrying a soft canvas tote bag slung over one shoulder, fabric folding naturally against the side" },
|
|
86
|
+
{ id: "briefcase", label: "Briefcase", category: "bag-accessory", description: "Hard-shell briefcase", promptHint: "carrying a hard-shell briefcase by the top handle in one hand at hip height, the case angled to read clearly in frame" },
|
|
87
|
+
{ id: "umbrella", label: "Umbrella", category: "bag-accessory", description: "Open black umbrella", promptHint: "holding an open black umbrella overhead with one hand, the canopy haloing the head and shadow falling across the face" },
|
|
88
|
+
{ id: "fan-folding", label: "Folding Fan", category: "bag-accessory", description: "Open hand-painted fan", promptHint: "holding an open hand-painted folding fan raised near the face, fingers fanned along the spine" },
|
|
89
|
+
{ id: "parasol", label: "Parasol", category: "bag-accessory", description: "Decorative Victorian / Asian parasol", promptHint: "holding a decorative parasol open above one shoulder, the lace or hand-painted canopy shielding the face from sun and casting a delicate dappled shadow" },
|
|
90
|
+
{ id: "locket", label: "Locket", category: "bag-accessory", description: "Open vintage locket pendant", promptHint: "holding an open vintage locket pendant between thumb and forefinger close to the chest, the tiny portrait inside angled gently toward the face" },
|
|
91
|
+
|
|
92
|
+
// -------------------- Floral / Nature --------------------
|
|
93
|
+
{ id: "bouquet", label: "Bouquet", category: "floral-nature", description: "Mixed bouquet of flowers", promptHint: "cradling a mixed bouquet of fresh flowers in both arms against the chest, blooms framing the face from below" },
|
|
94
|
+
{ id: "single-rose", label: "Single Rose", category: "floral-nature", description: "Single long-stem rose", promptHint: "holding a single long-stem red rose loosely between two fingers, the bloom raised softly toward the face" },
|
|
95
|
+
{ id: "sunflower", label: "Sunflower", category: "floral-nature", description: "Single tall sunflower", promptHint: "holding a tall sunflower upright by the stem in one hand, the wide yellow bloom raised slightly above the shoulder" },
|
|
96
|
+
{ id: "leaf", label: "Leaf", category: "floral-nature", description: "Single large leaf", promptHint: "holding a single large green leaf by its stem in one hand, the broad surface positioned to one side of the face" },
|
|
97
|
+
{ id: "fruit-apple", label: "Apple", category: "floral-nature", description: "Single fresh apple", promptHint: "holding a single fresh apple cradled in one open palm at chest height, the glossy skin catching the light" },
|
|
98
|
+
|
|
99
|
+
// -------------------- Instruments / Performance --------------------
|
|
100
|
+
{ id: "guitar", label: "Guitar", category: "instrument", description: "Guitar slung across body", promptHint: "holding a guitar slung across the body with one hand on the neck and the other resting over the strings near the soundhole" },
|
|
101
|
+
{ id: "violin", label: "Violin", category: "instrument", description: "Violin under chin", promptHint: "holding a violin tucked under the chin with one hand fingering the neck and the other drawing a bow across the strings" },
|
|
102
|
+
{ id: "saxophone", label: "Saxophone", category: "instrument", description: "Saxophone raised to lips", promptHint: "holding a saxophone raised to the lips with both hands wrapped around the keys, a strap supporting the weight at the neck" },
|
|
103
|
+
{ id: "drumsticks", label: "Drumsticks", category: "instrument", description: "Pair of drumsticks crossed", promptHint: "holding a pair of drumsticks crossed in front of the chest, fingers wrapped firmly around their grips" },
|
|
104
|
+
{ id: "sheet-music", label: "Sheet Music", category: "instrument", description: "Folded sheet music", promptHint: "holding a folded sheet of musical notation open between both hands at chest height, eyes lightly scanning the staves" },
|
|
105
|
+
|
|
106
|
+
// -------------------- Companion --------------------
|
|
107
|
+
{ id: "small-dog", label: "Small Dog", category: "companion", description: "Small dog held in arms", promptHint: "cradling a small dog in both arms against the chest, the pet's head resting comfortably under the subject's chin" },
|
|
108
|
+
{ id: "cat", label: "Cat", category: "companion", description: "Cat draped over arm", promptHint: "cradling a cat draped across one arm at chest height, the other hand resting gently along its back" },
|
|
109
|
+
{ id: "plush-toy", label: "Plush Toy", category: "companion", description: "Soft plush toy hugged", promptHint: "hugging a soft plush toy against the chest with both arms wrapped around it, head leaning lightly against its top" },
|
|
110
|
+
|
|
111
|
+
// -------------------- Occupational / Weapon --------------------
|
|
112
|
+
{ id: "katana", label: "Katana", category: "occupational", description: "Single-edged Japanese sword", promptHint: "holding a katana with one hand on the wrapped grip and the other resting on the long polished edge, blade angled diagonally across the body" },
|
|
113
|
+
{ id: "pointer-stick", label: "Pointer Stick", category: "occupational", description: "Telescoping pointer stick", promptHint: "holding a slender telescoping pointer stick in one hand, the tip raised toward an unseen surface as if mid-explanation" },
|
|
114
|
+
{ id: "gavel", label: "Gavel", category: "occupational", description: "Wooden judicial gavel", promptHint: "gripping a polished wooden gavel mid-strike with one hand, the head poised above an unseen sound block" },
|
|
115
|
+
{ id: "wine-bottle", label: "Wine Bottle", category: "occupational", description: "Full bottle with foil seal", promptHint: "holding a tall wine bottle by the neck with one hand, the labeled body and red foil seal angled clearly toward the camera" },
|
|
116
|
+
{ id: "lantern", label: "Lantern", category: "occupational", description: "Vintage handheld lantern with amber glow", promptHint: "carrying a vintage handheld lantern by its top ring at hip height, warm amber glow casting a soft circular pool of light across the face and surroundings" },
|
|
117
|
+
{ id: "flashlight", label: "Flashlight", category: "occupational", description: "Modern flashlight cutting a beam", promptHint: "holding a modern flashlight raised forward in one hand, a sharp white beam cutting through the darkness ahead and side-lighting the face" },
|
|
118
|
+
{ id: "compass", label: "Compass", category: "occupational", description: "Vintage handheld nautical compass", promptHint: "holding a vintage brass nautical compass open in one cupped palm at chest height, the needle clearly visible as the eyes drift down to read the bearing" },
|
|
119
|
+
{ id: "bow-and-arrow", label: "Bow and Arrow", category: "occupational", description: "Drawn archery bow with arrow nocked", promptHint: "holding an archery bow drawn at full tension with one hand on the grip and the other pulling the string back to the cheek, an arrow nocked and aimed forward" },
|
|
120
|
+
{ id: "shield", label: "Shield", category: "occupational", description: "Handheld medieval shield", promptHint: "holding a medieval shield raised across the body with one arm strapped through the back, the front face angled forward in a defensive stance" },
|
|
121
|
+
] as const
|
|
122
|
+
|
|
123
|
+
const heldPropById = new Map<string, HeldProp>(HELD_PROPS.map((p) => [p.id, p]))
|
|
124
|
+
|
|
125
|
+
export function getHeldProp(id: string | undefined | null): HeldProp | undefined {
|
|
126
|
+
if (!id) return undefined
|
|
127
|
+
return heldPropById.get(id)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function getHeldPropLabel(id: string | undefined | null, fallback?: string): string {
|
|
131
|
+
const p = getHeldProp(id)
|
|
132
|
+
if (p) return p.label
|
|
133
|
+
if (fallback !== undefined) return fallback
|
|
134
|
+
return (id ?? "").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function getHeldPropPromptHint(id: string | undefined | null): string {
|
|
138
|
+
return getHeldProp(id)?.promptHint ?? ""
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Multi-pick: 1-2 prop ids → composite held-prop clause. Single → entry's
|
|
143
|
+
* own promptHint (which already starts with "holding..." / "carrying..."
|
|
144
|
+
* grammar). Two → emit independently, joined by buildPersonHints-style
|
|
145
|
+
* comma-join. Common combos: book + coffee, cigarette + drink, phone + bag.
|
|
146
|
+
*/
|
|
147
|
+
export function buildHeldPropHints(value: unknown): string[] {
|
|
148
|
+
const ids: string[] = []
|
|
149
|
+
if (typeof value === "string" && value) ids.push(value)
|
|
150
|
+
else if (Array.isArray(value)) {
|
|
151
|
+
for (const v of value) {
|
|
152
|
+
if (typeof v === "string" && v && !ids.includes(v)) ids.push(v)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const out: string[] = []
|
|
156
|
+
for (const id of ids) {
|
|
157
|
+
const hint = getHeldPropPromptHint(id)
|
|
158
|
+
if (hint) out.push(hint)
|
|
159
|
+
}
|
|
160
|
+
return out
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const HELD_PROP_IDS: ReadonlyArray<string> = HELD_PROPS.map((p) => p.id)
|
|
164
|
+
|
|
165
|
+
export const HELD_PROP_CATEGORY_LABELS: Readonly<Record<HeldPropCategory, string>> = {
|
|
166
|
+
device: "Devices / Phones",
|
|
167
|
+
drink: "Drinks",
|
|
168
|
+
smoking: "Smoking",
|
|
169
|
+
"reading-writing": "Reading / Writing",
|
|
170
|
+
"bag-accessory": "Bags / Accessories",
|
|
171
|
+
"floral-nature": "Floral / Nature",
|
|
172
|
+
instrument: "Instruments / Performance",
|
|
173
|
+
companion: "Companions",
|
|
174
|
+
occupational: "Occupational",
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const HELD_PROP_CATEGORY_ORDER: ReadonlyArray<HeldPropCategory> = [
|
|
178
|
+
"device",
|
|
179
|
+
"drink",
|
|
180
|
+
"smoking",
|
|
181
|
+
"reading-writing",
|
|
182
|
+
"bag-accessory",
|
|
183
|
+
"floral-nature",
|
|
184
|
+
"instrument",
|
|
185
|
+
"companion",
|
|
186
|
+
"occupational",
|
|
187
|
+
]
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity Lock — strength control for facial likeness preservation when a
|
|
3
|
+
* Character node's reference photo is fed into downstream generation.
|
|
4
|
+
*
|
|
5
|
+
* Real prompts seen in the wild (especially on Nano-Banana-Pro and
|
|
6
|
+
* ChatGPT-Image) explicitly write `"identitylock": "priority absolute"` /
|
|
7
|
+
* `"strictvisualfidelity": true` to clamp the face to the reference. This
|
|
8
|
+
* helper produces the equivalent natural-language clause so the rest of the
|
|
9
|
+
* stack stays unchanged.
|
|
10
|
+
*
|
|
11
|
+
* Used by:
|
|
12
|
+
* - frontend DAG executor (`workflow-editor/execute-node.ts`)
|
|
13
|
+
* - backend orchestrator (`services/workflow-engine/payload-builder.ts`)
|
|
14
|
+
*/
|
|
15
|
+
import type { GenericNode, GenericEdge, ConnectedReference, ReferenceSource } from "@nodaro/shared"
|
|
16
|
+
import { PASSTHROUGH_TYPES } from "@nodaro/shared"
|
|
17
|
+
|
|
18
|
+
export type IdentityLockMode = "off" | "soft" | "strict"
|
|
19
|
+
|
|
20
|
+
/** Default applied when a Character node was created before the field existed. */
|
|
21
|
+
export const DEFAULT_IDENTITY_LOCK: IdentityLockMode = "soft"
|
|
22
|
+
|
|
23
|
+
/** Strict beats Soft beats Off — when multiple Character nodes feed one node. */
|
|
24
|
+
const RANK: Record<IdentityLockMode, number> = { off: 0, soft: 1, strict: 2 }
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Natural-language clause for each mode. Returns an empty string for "off"
|
|
28
|
+
* (or anything unrecognised) so callers can append unconditionally.
|
|
29
|
+
*/
|
|
30
|
+
export function getIdentityLockClause(mode: IdentityLockMode | undefined): string {
|
|
31
|
+
switch (mode) {
|
|
32
|
+
case "strict":
|
|
33
|
+
return "the subject's facial identity must match the reference photo exactly — no creative reinterpretation; preserve facial structure, eye color, skin tone, and distinctive features precisely."
|
|
34
|
+
case "soft":
|
|
35
|
+
return "preserve the reference subject's overall facial likeness."
|
|
36
|
+
default:
|
|
37
|
+
return ""
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Coerce arbitrary node-data field into a valid `IdentityLockMode`. */
|
|
42
|
+
export function toIdentityLockMode(value: unknown): IdentityLockMode {
|
|
43
|
+
if (value === "off" || value === "soft" || value === "strict") return value
|
|
44
|
+
return DEFAULT_IDENTITY_LOCK
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Per-mode lock wording for the CHARACTER NODE's off/soft/strict control —
|
|
49
|
+
* `{ref}`-BOUND variants of the `getIdentityLockClause` ladder. The binding
|
|
50
|
+
* matters: these are emitted as PER-REFERENCE lines (one per attached URL), so
|
|
51
|
+
* a multi-character scene must attribute each line to its reference image —
|
|
52
|
+
* unbound clauses would repeat the same anonymous "the subject…" sentence with
|
|
53
|
+
* no way to tell which reference either line governs (caught by adversarial
|
|
54
|
+
* review of PR #3735). Same escalation semantics as the config panel's
|
|
55
|
+
* off/soft/strict; only the phrasing is reference-bound.
|
|
56
|
+
*/
|
|
57
|
+
const CHARACTER_NODE_LOCK_TEXT: Record<Exclude<IdentityLockMode, "off">, string> = {
|
|
58
|
+
soft: "Preserve the overall facial likeness of the subject in {ref}.",
|
|
59
|
+
strict:
|
|
60
|
+
"The facial identity of the subject in {ref} must match exactly — no creative reinterpretation; preserve facial structure, eye color, skin tone, and distinctive features precisely.",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Map a Character NODE's `identityLock` mode (off/soft/strict) → the per-reference
|
|
65
|
+
* lock shape (`{ enabled, text }`) stamped onto every `ConnectedReference` derived
|
|
66
|
+
* from that node. This is what makes the node's off/soft/strict control reach
|
|
67
|
+
* hybrid output via `buildIdentityLockLine` (below):
|
|
68
|
+
* - off → { enabled: false } (no lock line)
|
|
69
|
+
* - soft → { enabled: true, text: <soft clause> } (mild "preserve likeness")
|
|
70
|
+
* - strict → { enabled: true, text: <strict clause> }(strong "match exactly")
|
|
71
|
+
* `undefined` coerces to the runtime default (`"soft"`) via `toIdentityLockMode`,
|
|
72
|
+
* so existing nodes (which never set the field) emit the mild line in hybrid. The
|
|
73
|
+
* per-mention `~lock`/`~nolock` sentinel still overrides via `withForcedIdentityLock`.
|
|
74
|
+
*/
|
|
75
|
+
export function characterLockToRefLock(
|
|
76
|
+
mode: IdentityLockMode | undefined,
|
|
77
|
+
): { enabled: boolean; text?: string } {
|
|
78
|
+
const m = toIdentityLockMode(mode)
|
|
79
|
+
if (m === "off") return { enabled: false }
|
|
80
|
+
return { enabled: true, text: CHARACTER_NODE_LOCK_TEXT[m] }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @deprecated Since Fix 4 (character @-mentions revamp) the per-image
|
|
85
|
+
* identity directive in `buildImagePrompt` — via
|
|
86
|
+
* `resolveCharacterMentions` Phase 0 and the strengthened
|
|
87
|
+
* `buildIdentityDirective` for person/character labels — already folds
|
|
88
|
+
* the identity-preservation language directly into the bulleted reference
|
|
89
|
+
* section. The global trailing clause this function used to produce is
|
|
90
|
+
* now redundant for character-wired flows, and this function only ever
|
|
91
|
+
* fired for upstream Character nodes. It now returns "" unconditionally
|
|
92
|
+
* so existing callers stay safe; remove the call sites in a follow-up
|
|
93
|
+
* cleanup, then this helper, `IdentityLockMode`, and `getIdentityLockClause`
|
|
94
|
+
* can be retired.
|
|
95
|
+
*/
|
|
96
|
+
export function collectIdentityLockClause<N extends GenericNode, E extends GenericEdge>(
|
|
97
|
+
_nodeId: string,
|
|
98
|
+
_nodes: readonly N[],
|
|
99
|
+
_edges: readonly E[],
|
|
100
|
+
): string {
|
|
101
|
+
return ""
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function collectStrongestIdentityLock<N extends GenericNode, E extends GenericEdge>(
|
|
105
|
+
nodeId: string,
|
|
106
|
+
nodes: readonly N[],
|
|
107
|
+
edges: readonly E[],
|
|
108
|
+
visited = new Set<string>(),
|
|
109
|
+
): IdentityLockMode | undefined {
|
|
110
|
+
if (visited.has(nodeId)) return undefined
|
|
111
|
+
visited.add(nodeId)
|
|
112
|
+
let strongest: IdentityLockMode | undefined
|
|
113
|
+
const incoming = edges.filter((e) => e.target === nodeId)
|
|
114
|
+
for (const edge of incoming) {
|
|
115
|
+
const src = nodes.find((n) => n.id === edge.source)
|
|
116
|
+
if (!src) continue
|
|
117
|
+
if (src.type === "character") {
|
|
118
|
+
const mode = toIdentityLockMode((src.data as Record<string, unknown>).identityLock)
|
|
119
|
+
if (!strongest || RANK[mode] > RANK[strongest]) strongest = mode
|
|
120
|
+
}
|
|
121
|
+
// Pass-through types don't produce refs themselves but may forward an
|
|
122
|
+
// upstream Character (e.g. ai-writer wired to a Character then to image-gen).
|
|
123
|
+
if (PASSTHROUGH_TYPES.has(src.type)) {
|
|
124
|
+
const upstream = collectStrongestIdentityLock(src.id, nodes, edges, visited)
|
|
125
|
+
if (upstream && (!strongest || RANK[upstream] > RANK[strongest])) strongest = upstream
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return strongest
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Return `true` when any Character node is upstream of `nodeId` (walking
|
|
133
|
+
* through pass-through types). Used to short-circuit
|
|
134
|
+
* `collectIdentityLockClause`: with the new per-image identity directives
|
|
135
|
+
* (Fix 4) the global trailing clause is redundant for character-wired flows.
|
|
136
|
+
*
|
|
137
|
+
* Non-character wired-image refs still get the global clause.
|
|
138
|
+
*/
|
|
139
|
+
export function hasUpstreamCharacter<N extends GenericNode, E extends GenericEdge>(
|
|
140
|
+
nodeId: string,
|
|
141
|
+
nodes: readonly N[],
|
|
142
|
+
edges: readonly E[],
|
|
143
|
+
visited = new Set<string>(),
|
|
144
|
+
): boolean {
|
|
145
|
+
if (visited.has(nodeId)) return false
|
|
146
|
+
visited.add(nodeId)
|
|
147
|
+
const incoming = edges.filter((e) => e.target === nodeId)
|
|
148
|
+
for (const edge of incoming) {
|
|
149
|
+
const src = nodes.find((n) => n.id === edge.source)
|
|
150
|
+
if (!src) continue
|
|
151
|
+
if (src.type === "character") return true
|
|
152
|
+
if (PASSTHROUGH_TYPES.has(src.type)) {
|
|
153
|
+
if (hasUpstreamCharacter(src.id, nodes, edges, visited)) return true
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return false
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Per-reference, customizable identity-lock (Unified Reference Roles, Phase A).
|
|
161
|
+
//
|
|
162
|
+
// Distinct from the node-data `IdentityLockMode` ("off"/"soft"/"strict") above:
|
|
163
|
+
// this is an OPTIONAL, opt-in lock line attached to a single resolved
|
|
164
|
+
// `ConnectedReference`. OFF by default for EVERY source — a lock line is emitted
|
|
165
|
+
// only when the ref explicitly sets `identityLock.enabled === true` (the ref may
|
|
166
|
+
// supply custom `.text`; otherwise the source's built-in wording is used).
|
|
167
|
+
// Consumed by the prompt builder to prepend a fidelity line per reference.
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
/** Built-in lock wording per source. `{ref}` is replaced with the binding. */
|
|
171
|
+
const DEFAULT_LOCK_TEXT: Partial<Record<ReferenceSource, string>> = {
|
|
172
|
+
"wired-character": "Lock the exact identity of the person in {ref} — face, bone structure, skin tone, and all unique features.",
|
|
173
|
+
"wired-face": "Lock the exact facial identity in {ref} — bone structure, features, and skin texture.",
|
|
174
|
+
"wired-creature": "Lock the exact identity of the creature in {ref} — anatomy, markings, and all unique features.",
|
|
175
|
+
"wired-location": "Lock the exact look of {ref} — match the location's architecture, layout, and lighting.",
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The identity-lock line for a reference, or `null` when the lock is off.
|
|
180
|
+
* Opt-in only: a line is returned ONLY when `identityLock.enabled === true`
|
|
181
|
+
* (absent or `enabled === false` → `null`). Custom `text` wins (with `{ref}` →
|
|
182
|
+
* binding); otherwise the source's built-in wording is used.
|
|
183
|
+
*/
|
|
184
|
+
/**
|
|
185
|
+
* Apply a per-mention identity-lock OVERRIDE to `ref` — the tri-state
|
|
186
|
+
* `~lock` / `~nolock` sentinel (Unified Reference Roles, Task 4 + F4). The
|
|
187
|
+
* `lockOverride` argument is the token's parsed `lock`:
|
|
188
|
+
* - `undefined` → INHERIT: the ORIGINAL ref is returned UNCHANGED (never a
|
|
189
|
+
* copy), so a lock-less mention resolves byte-identically to before and the
|
|
190
|
+
* ref/source default governs.
|
|
191
|
+
* - `true` → FORCE ON: `identityLock.enabled = true` (any existing custom
|
|
192
|
+
* `text` is preserved) — the `~lock` sentinel.
|
|
193
|
+
* - `false` → FORCE OFF: `identityLock.enabled = false`, which
|
|
194
|
+
* `buildIdentityLockLine` renders as `null`, SUPPRESSING even a ref-level
|
|
195
|
+
* `identityLock.enabled = true` — the `~nolock` sentinel.
|
|
196
|
+
* Called by the three HYBRID mention resolvers right before
|
|
197
|
+
* `buildIdentityLockLine`. Never mutates `ref`.
|
|
198
|
+
*/
|
|
199
|
+
export function withForcedIdentityLock(ref: ConnectedReference, lockOverride?: boolean): ConnectedReference {
|
|
200
|
+
if (lockOverride === undefined) return ref
|
|
201
|
+
if (lockOverride) return { ...ref, identityLock: { enabled: true, text: ref.identityLock?.text } }
|
|
202
|
+
return { ...ref, identityLock: { enabled: false } }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function buildIdentityLockLine(ref: ConnectedReference, binding: string): string | null {
|
|
206
|
+
const lock = ref.identityLock
|
|
207
|
+
if (!lock?.enabled) return null // default OFF + explicit-false OFF
|
|
208
|
+
const interpolate = (t: string) => t.replaceAll("{ref}", binding)
|
|
209
|
+
const custom = lock.text?.trim()
|
|
210
|
+
if (custom) return interpolate(custom)
|
|
211
|
+
const text = DEFAULT_LOCK_TEXT[ref.source]
|
|
212
|
+
return text ? interpolate(text) : null
|
|
213
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nodaro/prompts — creative/prompt IP shared across backend and
|
|
3
|
+
* frontend but deliberately EXCLUDED from the published Apache packages.
|
|
4
|
+
* Never published to npm ("private": true); licensed with the repository
|
|
5
|
+
* core under the root Nodaro Sustainable Use License.
|
|
6
|
+
*
|
|
7
|
+
* Placement rule (root CLAUDE.md): new prompt engineering, catalogs,
|
|
8
|
+
* doctrine, and presets default to backend/ or here — packages/shared gets
|
|
9
|
+
* only what the public API contract requires.
|
|
10
|
+
*/
|
|
11
|
+
export * from "./identity-lock.js"
|
|
12
|
+
export * from "./parameter-prompt-hint.js"
|
|
13
|
+
export * from "./entity-prompts.js"
|
|
14
|
+
export * from "./brand-tokens.js"
|
|
15
|
+
export * from "./prompt-builder.js"
|
|
16
|
+
export * from "./prompt-builder-structured-fields.js"
|
|
17
|
+
export * from "./video-reference-resolver.js"
|
|
18
|
+
export * from "./sound-aggregator.js"
|
|
19
|
+
export * from "./assemble-suno-input.js"
|
|
20
|
+
export * from "./assemble-image-input.js"
|
|
21
|
+
export * from "./seedance-2-inputs.js"
|
|
22
|
+
export * from "./person.js"
|
|
23
|
+
export * from "./picker-catalogs.js"
|
|
24
|
+
export * from "./picker-analyzer-registry.js"
|
|
25
|
+
export * from "./action-fx.js"
|
|
26
|
+
export * from "./aesthetic.js"
|
|
27
|
+
export * from "./atmosphere.js"
|
|
28
|
+
export * from "./backdrop.js"
|
|
29
|
+
export * from "./camera-format.js"
|
|
30
|
+
export * from "./camera-motions.js"
|
|
31
|
+
export * from "./character-fx.js"
|
|
32
|
+
export * from "./color-look.js"
|
|
33
|
+
export * from "./composition-effects.js"
|
|
34
|
+
export * from "./era.js"
|
|
35
|
+
export * from "./exposure-settings.js"
|
|
36
|
+
export * from "./framing.js"
|
|
37
|
+
export * from "./held-prop.js"
|
|
38
|
+
export * from "./instrumentation.js"
|
|
39
|
+
export * from "./lens.js"
|
|
40
|
+
export * from "./lighting.js"
|
|
41
|
+
export * from "./loop-subject.js"
|
|
42
|
+
export * from "./materials.js"
|
|
43
|
+
export * from "./mood.js"
|
|
44
|
+
export * from "./music-genre.js"
|
|
45
|
+
export * from "./music-mood.js"
|
|
46
|
+
export * from "./photo-genre.js"
|
|
47
|
+
export * from "./photographer.js"
|
|
48
|
+
export * from "./pose.js"
|
|
49
|
+
export * from "./post-process-effects.js"
|
|
50
|
+
export * from "./render-quality.js"
|
|
51
|
+
export * from "./setting.js"
|
|
52
|
+
export * from "./style.js"
|
|
53
|
+
export * from "./styling.js"
|
|
54
|
+
export * from "./temporal.js"
|
|
55
|
+
export * from "./transitions.js"
|
|
56
|
+
export * from "./voice-character.js"
|
|
57
|
+
export * from "./voice-delivery.js"
|
|
58
|
+
export * from "./wardrobe.js"
|
|
59
|
+
export * from "./prompt-templates.js"
|
|
60
|
+
export * from "./provider-prompt-doctrine.js"
|
|
61
|
+
export * from "./prompt-wizard-categories.js"
|
|
62
|
+
export * from "./resolve-prompt.js"
|
|
63
|
+
export * from "./factory-presets.js"
|
|
64
|
+
export * from "./style-presets.js"
|
|
65
|
+
export * from "./object-asset-presets.js"
|
|
66
|
+
export * from "./factory-snippets/index.js"
|