@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.
Files changed (150) hide show
  1. package/LICENSE +105 -0
  2. package/README.md +27 -0
  3. package/dist/index.cjs +21240 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +3599 -0
  6. package/dist/index.d.ts +3599 -0
  7. package/dist/index.js +20854 -0
  8. package/dist/index.js.map +1 -0
  9. package/package.json +49 -0
  10. package/src/__tests__/__snapshots__/prompt-builder-segments.test.ts.snap +65 -0
  11. package/src/__tests__/action-fx.test.ts +155 -0
  12. package/src/__tests__/apply-picker-json.test.ts +50 -0
  13. package/src/__tests__/assemble-image-input.test.ts +248 -0
  14. package/src/__tests__/assemble-suno-input.test.ts +283 -0
  15. package/src/__tests__/brand-tokens.test.ts +81 -0
  16. package/src/__tests__/build-image-prompt-element-injection.test.ts +118 -0
  17. package/src/__tests__/build-image-prompt-hybrid-format.test.ts +131 -0
  18. package/src/__tests__/build-image-prompt-mentions.test.ts +809 -0
  19. package/src/__tests__/build-image-prompt-reference-cap.test.ts +57 -0
  20. package/src/__tests__/build-image-prompt-reference-numbering.test.ts +237 -0
  21. package/src/__tests__/build-image-prompt-reference-order.test.ts +313 -0
  22. package/src/__tests__/camera-motions-from-connections.test.ts +71 -0
  23. package/src/__tests__/catalog-gapfill.test.ts +53 -0
  24. package/src/__tests__/character-convergence-image.test.ts +217 -0
  25. package/src/__tests__/character-default-role-image.test.ts +167 -0
  26. package/src/__tests__/character-default-role-video.test.ts +168 -0
  27. package/src/__tests__/character-default-role.test.ts +70 -0
  28. package/src/__tests__/character-fx.test.ts +200 -0
  29. package/src/__tests__/entity-prompts-location.test.ts +97 -0
  30. package/src/__tests__/entity-prompts.test.ts +240 -0
  31. package/src/__tests__/expand-extra-refs-role.test.ts +50 -0
  32. package/src/__tests__/factory-presets.test.ts +1045 -0
  33. package/src/__tests__/factory-snippets.test.ts +68 -0
  34. package/src/__tests__/framing-multi.test.ts +71 -0
  35. package/src/__tests__/framing-vantage.test.ts +39 -0
  36. package/src/__tests__/i18n-entry-completeness.test.ts +269 -0
  37. package/src/__tests__/identity-lock.test.ts +46 -0
  38. package/src/__tests__/instrumentation.test.ts +68 -0
  39. package/src/__tests__/lighting-multi.test.ts +68 -0
  40. package/src/__tests__/location-convergence-image.test.ts +124 -0
  41. package/src/__tests__/mention-lock-flag.test.ts +499 -0
  42. package/src/__tests__/multi-picker-spec.test.ts +71 -0
  43. package/src/__tests__/music-genre.test.ts +103 -0
  44. package/src/__tests__/music-mood.test.ts +87 -0
  45. package/src/__tests__/object-creature-convergence-image.test.ts +105 -0
  46. package/src/__tests__/parameter-prompt-hint.test.ts +270 -0
  47. package/src/__tests__/parameter-registry-sync.test.ts +243 -0
  48. package/src/__tests__/person-age.test.ts +80 -0
  49. package/src/__tests__/person-analyzer-invariants.test.ts +47 -0
  50. package/src/__tests__/person-body-axes.test.ts +67 -0
  51. package/src/__tests__/person-facial-geometry.test.ts +156 -0
  52. package/src/__tests__/person-regional-aesthetic.test.ts +161 -0
  53. package/src/__tests__/person-sections.test.ts +18 -0
  54. package/src/__tests__/picker-analyzer-registry.test.ts +108 -0
  55. package/src/__tests__/picker-catalogs-project.test.ts +85 -0
  56. package/src/__tests__/picker-catalogs.test.ts +53 -0
  57. package/src/__tests__/picker-limits.test.ts +20 -0
  58. package/src/__tests__/prompt-builder-segments.test.ts +183 -0
  59. package/src/__tests__/prompt-builder-structured-fields.test.ts +40 -0
  60. package/src/__tests__/prompt-builder.test.ts +1773 -0
  61. package/src/__tests__/prompt-wizard-categories.test.ts +31 -0
  62. package/src/__tests__/provider-prompt-doctrine.test.ts +49 -0
  63. package/src/__tests__/resolve-prompt-append.test.ts +58 -0
  64. package/src/__tests__/resolve-prompt.test.ts +44 -0
  65. package/src/__tests__/role-picker-shared.test.ts +208 -0
  66. package/src/__tests__/seedance-2-inputs.test.ts +173 -0
  67. package/src/__tests__/seedance-extend.test.ts +44 -0
  68. package/src/__tests__/sound-aggregator.test.ts +350 -0
  69. package/src/__tests__/style-presets.test.ts +34 -0
  70. package/src/__tests__/temporal-multi.test.ts +74 -0
  71. package/src/__tests__/transitions.test.ts +213 -0
  72. package/src/__tests__/video-reference-features.test.ts +41 -0
  73. package/src/__tests__/video-reference-leading-refs.test.ts +90 -0
  74. package/src/__tests__/video-reference-resolver.test.ts +233 -0
  75. package/src/__tests__/video-reference-roles.test.ts +96 -0
  76. package/src/__tests__/voice-character.test.ts +48 -0
  77. package/src/__tests__/voice-delivery.test.ts +41 -0
  78. package/src/__tests__/wardrobe.test.ts +25 -0
  79. package/src/action-fx.ts +255 -0
  80. package/src/aesthetic.ts +435 -0
  81. package/src/assemble-image-input.ts +236 -0
  82. package/src/assemble-suno-input.ts +147 -0
  83. package/src/atmosphere.ts +104 -0
  84. package/src/backdrop.ts +131 -0
  85. package/src/brand-tokens.ts +154 -0
  86. package/src/camera-format.ts +76 -0
  87. package/src/camera-motions.ts +615 -0
  88. package/src/character-fx.ts +274 -0
  89. package/src/color-look.ts +103 -0
  90. package/src/composition-effects.ts +67 -0
  91. package/src/entity-prompts.ts +231 -0
  92. package/src/era.ts +292 -0
  93. package/src/exposure-settings.ts +142 -0
  94. package/src/factory-presets/generate-image.ts +1644 -0
  95. package/src/factory-presets/generate-video.ts +1116 -0
  96. package/src/factory-presets/index.ts +46 -0
  97. package/src/factory-presets/lottie-overlay.ts +166 -0
  98. package/src/factory-presets/motion-graphics.ts +350 -0
  99. package/src/factory-presets/music.ts +734 -0
  100. package/src/factory-presets/sfx.ts +136 -0
  101. package/src/factory-presets/shared-image.ts +207 -0
  102. package/src/factory-presets/switchx.ts +65 -0
  103. package/src/factory-presets/text.ts +292 -0
  104. package/src/factory-presets/types.ts +51 -0
  105. package/src/factory-presets/video-edit.ts +136 -0
  106. package/src/factory-presets/voice.ts +172 -0
  107. package/src/factory-presets.ts +2 -0
  108. package/src/factory-snippets/catalog.ts +105 -0
  109. package/src/factory-snippets/index.ts +16 -0
  110. package/src/factory-snippets/types.ts +33 -0
  111. package/src/framing.ts +634 -0
  112. package/src/held-prop.ts +187 -0
  113. package/src/identity-lock.ts +213 -0
  114. package/src/index.ts +66 -0
  115. package/src/instrumentation.ts +337 -0
  116. package/src/lens.ts +59 -0
  117. package/src/lighting.ts +229 -0
  118. package/src/loop-subject.ts +276 -0
  119. package/src/materials.ts +184 -0
  120. package/src/mood.ts +186 -0
  121. package/src/music-genre.ts +662 -0
  122. package/src/music-mood.ts +137 -0
  123. package/src/object-asset-presets.ts +81 -0
  124. package/src/parameter-prompt-hint.ts +281 -0
  125. package/src/person.ts +1368 -0
  126. package/src/photo-genre.ts +151 -0
  127. package/src/photographer.ts +612 -0
  128. package/src/picker-analyzer-registry.ts +374 -0
  129. package/src/picker-catalogs.ts +858 -0
  130. package/src/pose.ts +246 -0
  131. package/src/post-process-effects.ts +94 -0
  132. package/src/prompt-builder-structured-fields.ts +116 -0
  133. package/src/prompt-builder.ts +2964 -0
  134. package/src/prompt-templates.ts +50 -0
  135. package/src/prompt-wizard-categories.ts +334 -0
  136. package/src/provider-prompt-doctrine.ts +85 -0
  137. package/src/render-quality.ts +89 -0
  138. package/src/resolve-prompt.ts +120 -0
  139. package/src/seedance-2-inputs.ts +100 -0
  140. package/src/setting.ts +130 -0
  141. package/src/sound-aggregator.ts +241 -0
  142. package/src/style-presets.ts +162 -0
  143. package/src/style.ts +99 -0
  144. package/src/styling.ts +585 -0
  145. package/src/temporal.ts +151 -0
  146. package/src/transitions.ts +333 -0
  147. package/src/video-reference-resolver.ts +823 -0
  148. package/src/voice-character.ts +237 -0
  149. package/src/voice-delivery.ts +142 -0
  150. package/src/wardrobe.ts +179 -0
package/src/pose.ts ADDED
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Canonical catalog of Pose / posture + action choices.
3
+ *
4
+ * Single-pick parameter node — user picks ONE pose that describes the
5
+ * subject's body position + action. Covers both static poses ("standing
6
+ * upright") and dynamic action ("mid-stride, running").
7
+ *
8
+ * Separate from:
9
+ * - Framing → Vantage (azimuth of camera around subject, not subject pose)
10
+ * - Camera Motion (how the camera moves, not the subject)
11
+ *
12
+ * Applies to both image and video consumers (pose describes the subject,
13
+ * not video-specific). Not in STILL_IMAGE_EXCLUDE_TYPES.
14
+ *
15
+ * Includes pre/post free-text fields (same pattern as Person) for
16
+ * specifics the catalog can't express ("mid-fall, arms flailing", etc.).
17
+ *
18
+ * Shared between the picker UI, the standalone Pose parameter node, and
19
+ * the prompt-hint injection on both the frontend DAG executor and the
20
+ * backend orchestrator.
21
+ */
22
+
23
+ export type PoseCategory =
24
+ | "standing"
25
+ | "seated"
26
+ | "movement"
27
+ | "action"
28
+ | "resting"
29
+ | "hand-position"
30
+ | "body-lean"
31
+ | "head-tilt"
32
+ | "activity"
33
+
34
+ export interface Pose {
35
+ readonly id: string
36
+ readonly label: string
37
+ readonly category: PoseCategory
38
+ readonly description: string
39
+ readonly promptHint: string
40
+ }
41
+
42
+ export const POSES: ReadonlyArray<Pose> = [
43
+ // -------------------- Standing --------------------
44
+ { id: "standing-upright", label: "Standing Upright", category: "standing", description: "Relaxed standing posture", promptHint: "standing upright in a relaxed natural posture" },
45
+ { id: "confident-stance", label: "Confident Stance", category: "standing", description: "Feet apart, shoulders back", promptHint: "standing in a confident stance with feet apart, shoulders back, and head up" },
46
+ { id: "hands-on-hips", label: "Hands on Hips", category: "standing", description: "Hands on hips", promptHint: "standing with hands on hips" },
47
+ { id: "arms-crossed", label: "Arms Crossed", category: "standing", description: "Arms folded across chest", promptHint: "standing with arms crossed over the chest" },
48
+ { id: "leaning", label: "Leaning", category: "standing", description: "Leaning against something", promptHint: "leaning casually against a wall or surface" },
49
+ { id: "hero-pose", label: "Hero Pose", category: "standing", description: "Dramatic heroic stance", promptHint: "standing in a dramatic heroic pose, chest out, looking forward with purpose" },
50
+ { id: "contrapposto", label: "Contrapposto", category: "standing", description: "Hip tilted, weight on one leg", promptHint: "standing in contrapposto, weight shifted to one leg with the opposite hip tilted out" },
51
+ { id: "leaning-against-wall", label: "Leaning Against Wall", category: "standing", description: "Casually leaning on a wall", promptHint: "leaning casually against a wall, shoulder or back resting on the surface" },
52
+ { id: "hands-behind-head", label: "Hands Behind Head", category: "standing", description: "Both hands clasped behind head", promptHint: "standing with both hands clasped behind the head, elbows out" },
53
+ { id: "hands-behind-back", label: "Hands Behind Back", category: "standing", description: "Hands clasped behind back", promptHint: "standing with hands clasped behind the back, posture upright" },
54
+
55
+ // -------------------- Seated --------------------
56
+ { id: "sitting", label: "Sitting", category: "seated", description: "Sitting naturally", promptHint: "sitting naturally" },
57
+ { id: "cross-legged", label: "Cross-legged", category: "seated", description: "Seated cross-legged on floor", promptHint: "sitting cross-legged on the ground" },
58
+ { id: "kneeling", label: "Kneeling", category: "seated", description: "Kneeling on the ground", promptHint: "kneeling on one or both knees" },
59
+ { id: "crouching", label: "Crouching", category: "seated", description: "Crouched low", promptHint: "crouched low with knees bent" },
60
+ { id: "lounging", label: "Lounging", category: "seated", description: "Reclined, relaxed sitting", promptHint: "lounging in a relaxed, reclined position" },
61
+ { id: "sitting-edge-of-bed", label: "Sitting on Edge of Bed", category: "seated", description: "Perched on the edge of a bed", promptHint: "perched on the edge of a bed, hands resting on the mattress" },
62
+ { id: "chair-arm-drape", label: "Legs Draped Over Chair", category: "seated", description: "Legs draped over chair arm", promptHint: "sitting sideways in a chair with legs draped casually over one arm" },
63
+ { id: "elbow-propped", label: "Cheek on Propped Elbow", category: "seated", description: "Cheek resting on a propped elbow", promptHint: "seated with cheek resting against a propped-up elbow, contemplative" },
64
+ { id: "lying-on-stomach-reading", label: "Lying Prone Reading", category: "seated", description: "Lying prone, propped on elbows reading", promptHint: "lying on the stomach, propped up on elbows while reading" },
65
+
66
+ // -------------------- Movement --------------------
67
+ { id: "walking", label: "Walking", category: "movement", description: "Mid-stride walking", promptHint: "walking, caught mid-stride" },
68
+ { id: "running", label: "Running", category: "movement", description: "Mid-run, in motion", promptHint: "running, caught mid-stride in motion" },
69
+ { id: "jumping", label: "Jumping", category: "movement", description: "Airborne, mid-jump", promptHint: "mid-jump, suspended in the air" },
70
+ { id: "dancing", label: "Dancing", category: "movement", description: "Caught mid-dance", promptHint: "dancing, caught in mid-movement with fluid grace" },
71
+ { id: "climbing", label: "Climbing", category: "movement", description: "Climbing, gripping upward", promptHint: "climbing with arms extended, gripping upward" },
72
+ { id: "mid-fall", label: "Mid-Fall", category: "movement", description: "Caught mid-fall through the air", promptHint: "caught mid-fall through the air, limbs loose, hair and clothing trailing" },
73
+ { id: "mid-spin", label: "Mid-Spin", category: "movement", description: "Twirling, mid-rotation", promptHint: "twirling mid-spin with hair and clothing fanning outward" },
74
+ { id: "stretching", label: "Stretching", category: "movement", description: "Full-body stretch, arms overhead", promptHint: "in a full-body stretch with both arms reaching overhead" },
75
+ { id: "reaching-up", label: "Reaching Up", category: "movement", description: "Arms extended overhead", promptHint: "one or both arms extended overhead, reaching upward" },
76
+ { id: "kissing", label: "Kissing", category: "movement", description: "Locked in a kiss", promptHint: "locked in a kiss, faces close and eyes closed" },
77
+ { id: "riding", label: "Riding", category: "movement", description: "Riding a bike, horse, or motorcycle", promptHint: "riding astride a bike, horse, or motorcycle, hands gripping forward" },
78
+ { id: "driving", label: "Driving", category: "movement", description: "Behind the wheel of a vehicle", promptHint: "behind the wheel of a vehicle, hands on the steering wheel, gaze forward" },
79
+
80
+ // -------------------- Action --------------------
81
+ { id: "fighting-stance", label: "Fighting Stance", category: "action", description: "Combat-ready stance", promptHint: "in a combat-ready fighting stance, tensed and focused" },
82
+ { id: "reaching", label: "Reaching", category: "action", description: "Reaching outward", promptHint: "reaching outward with one arm extended" },
83
+ { id: "throwing", label: "Throwing", category: "action", description: "Mid-throw motion", promptHint: "caught mid-throw, body coiled and releasing" },
84
+ { id: "leaping", label: "Leaping", category: "action", description: "Leaping forward dynamically", promptHint: "leaping forward dynamically with body extended" },
85
+ { id: "dramatic-action", label: "Dramatic Action", category: "action", description: "Exaggerated action pose", promptHint: "in a dramatic, exaggerated action pose full of motion" },
86
+ { id: "biting-lip", label: "Biting Lip", category: "action", description: "Slight playful lip-bite", promptHint: "biting the lower lip with a subtle playful expression" },
87
+ { id: "mid-laugh", label: "Mid-Laugh", category: "action", description: "Caught mid-laugh, head back", promptHint: "caught mid-laugh with head tipped back, eyes crinkled" },
88
+ { id: "pointing-at-camera", label: "Pointing at Camera", category: "action", description: "Pointing directly at camera", promptHint: "pointing one finger directly at the camera, arm extended" },
89
+ { id: "tongue-out", label: "Sticking Tongue Out", category: "action", description: "Playful tongue-out expression", promptHint: "sticking the tongue out playfully" },
90
+ { id: "thinking", label: "Thinking", category: "action", description: "Hand on chin, contemplative", promptHint: "in a thinking pose with hand on chin, gaze contemplative" },
91
+
92
+ // -------------------- Resting --------------------
93
+ { id: "lying-down", label: "Lying Down", category: "resting", description: "Lying flat", promptHint: "lying down flat, relaxed" },
94
+ { id: "sleeping", label: "Sleeping", category: "resting", description: "Eyes closed, sleeping", promptHint: "sleeping peacefully with eyes closed" },
95
+ { id: "hugging", label: "Hugging", category: "resting", description: "Embracing another", promptHint: "hugging or embracing another person" },
96
+ { id: "looking-away", label: "Looking Away", category: "resting", description: "Head turned, looking away", promptHint: "head turned, looking off away from the camera" },
97
+ { id: "looking-up", label: "Looking Up", category: "resting", description: "Gazing up at the sky", promptHint: "gazing upward at the sky or off-camera, chin slightly raised" },
98
+ { id: "looking-down", label: "Looking Down", category: "resting", description: "Eyes downcast", promptHint: "eyes downcast, gaze turned softly to the ground" },
99
+ { id: "head-over-shoulder", label: "Head Over Shoulder", category: "resting", description: "Looking back over shoulder", promptHint: "head turned to look back over one shoulder toward the camera" },
100
+ { id: "wading-in-water", label: "Wading in Water", category: "resting", description: "Wading mid-thigh deep in water", promptHint: "wading mid-thigh deep through water, ripples around the legs" },
101
+
102
+ // -------------------- Hand Position --------------------
103
+ { id: "hands-in-pockets", label: "Hands in Pockets", category: "hand-position", description: "Both hands tucked in pockets", promptHint: "with both hands tucked into pockets" },
104
+ { id: "hand-on-hip", label: "Hand on Hip", category: "hand-position", description: "One hand on hip", promptHint: "with one hand resting on the hip" },
105
+ { id: "hand-position-hands-on-hips", label: "Hands on Hips", category: "hand-position", description: "Both hands on hips", promptHint: "with both hands planted on the hips" },
106
+ { id: "hand-on-chin", label: "Hand on Chin", category: "hand-position", description: "Hand resting under the chin", promptHint: "with one hand resting under the chin, fingers framing the jaw" },
107
+ { id: "hand-on-collarbone", label: "Hand on Collarbone", category: "hand-position", description: "Hand resting across collarbone", promptHint: "with one hand resting lightly across the collarbone" },
108
+ { id: "hand-brushing-hair", label: "Hand Brushing Hair", category: "hand-position", description: "Hand running through the hair", promptHint: "with a hand running through the hair, fingers caught mid-strand" },
109
+ { id: "finger-to-lip", label: "Finger to Lip", category: "hand-position", description: "Fingertip pressed against lower lip", promptHint: "with one fingertip pressed gently against the lower lip" },
110
+ { id: "arms-wrapped-around-self", label: "Arms Wrapped Around Self", category: "hand-position", description: "Self-hug, arms around torso", promptHint: "arms wrapped around the torso in a self-hug" },
111
+ { id: "hands-clasped", label: "Hands Clasped", category: "hand-position", description: "Both hands clasped at the front", promptHint: "with both hands clasped at the front" },
112
+
113
+ // -------------------- Body Lean --------------------
114
+ { id: "leaning-back", label: "Leaning Back", category: "body-lean", description: "Torso leaning back slightly", promptHint: "with the torso leaning back at a slight backward angle" },
115
+ { id: "leaning-forward", label: "Leaning Forward", category: "body-lean", description: "Torso leaning toward camera", promptHint: "with the torso leaning forward toward the camera" },
116
+ { id: "body-lean-contrapposto", label: "Contrapposto", category: "body-lean", description: "Weight on one leg, hip pushed out", promptHint: "with the weight on one leg and one hip pushed out, classical contrapposto" },
117
+ { id: "arched-back", label: "Arched Back", category: "body-lean", description: "Back gently arched, chest forward", promptHint: "with the back gently arched, chest forward" },
118
+ { id: "shoulder-rolled-forward", label: "Shoulder Rolled Forward", category: "body-lean", description: "One shoulder rolled forward", promptHint: "with one shoulder rolled forward, asymmetric stance" },
119
+
120
+ // -------------------- Head Tilt --------------------
121
+ { id: "tilted-up", label: "Tilted Up", category: "head-tilt", description: "Head tipped slightly upward", promptHint: "with the head tipped slightly upward" },
122
+ { id: "tilted-down", label: "Tilted Down", category: "head-tilt", description: "Head tipped slightly downward", promptHint: "with the head tipped slightly downward" },
123
+ { id: "tilted-side", label: "Tilted Side", category: "head-tilt", description: "Head tilted toward shoulder", promptHint: "with the head tilted toward one shoulder" },
124
+ { id: "tilted-back", label: "Tilted Back", category: "head-tilt", description: "Head fully back, throat exposed", promptHint: "with the head tipped fully back, exposing the throat" },
125
+ { id: "chin-up", label: "Chin Up", category: "head-tilt", description: "Chin lifted, looking down nose", promptHint: "with the chin lifted, looking down the nose" },
126
+ { id: "chin-tucked", label: "Chin Tucked", category: "head-tilt", description: "Chin tucked toward chest", promptHint: "with the chin tucked toward the chest" },
127
+
128
+ // -------------------- Activity (what the subject is doing in the world) --------------------
129
+ { id: "activity-smoking", label: "Smoking", category: "activity", description: "Holding and smoking a cigarette", promptHint: "holding and smoking a cigarette, smoke curling up from the lit tip" },
130
+ { id: "activity-drinking", label: "Drinking", category: "activity", description: "Drinking from a glass or cup", promptHint: "raising a glass or cup to the lips and drinking from it" },
131
+ { id: "activity-eating", label: "Eating", category: "activity", description: "Caught mid-bite", promptHint: "caught mid-bite, eating with a piece of food halfway to the mouth" },
132
+ { id: "activity-talking-on-phone", label: "Talking on Phone", category: "activity", description: "Phone held to ear, speaking", promptHint: "holding a phone to one ear and speaking into it, eyes focused mid-conversation" },
133
+ { id: "activity-texting", label: "Texting", category: "activity", description: "Looking down at phone, thumbs typing", promptHint: "looking down at a phone held in both hands, thumbs busy typing a message" },
134
+ { id: "activity-typing-laptop", label: "Typing on Laptop", category: "activity", description: "Hands on keyboard, focused on screen", promptHint: "typing at a laptop with hands on the keyboard, focused on the glowing screen" },
135
+ { id: "activity-reading", label: "Reading", category: "activity", description: "Holding open a book or magazine", promptHint: "holding open a book or magazine in both hands, eyes following the page" },
136
+ { id: "activity-writing", label: "Writing", category: "activity", description: "Writing in a notebook with a pen", promptHint: "writing in a notebook, pen in hand, head bent slightly over the page" },
137
+ { id: "activity-painting", label: "Painting", category: "activity", description: "Painting on a canvas with a brush", promptHint: "painting on a canvas, brush in hand mid-stroke, palette held in the other" },
138
+ { id: "activity-playing-instrument", label: "Playing Instrument", category: "activity", description: "Playing a musical instrument", promptHint: "playing a musical instrument, hands and fingers engaged with strings or keys, fully absorbed in the performance" },
139
+ { id: "activity-cooking", label: "Cooking", category: "activity", description: "Cooking at a kitchen counter or stove", promptHint: "cooking at a kitchen counter or stove, hands actively working with food, steam rising from a pan" },
140
+ { id: "activity-driving", label: "Driving", category: "activity", description: "Behind the wheel, hands gripping", promptHint: "behind the wheel of a vehicle, both hands on the steering wheel, gaze fixed on the road ahead" },
141
+ ] as const
142
+
143
+ const poseById = new Map<string, Pose>(POSES.map((p) => [p.id, p]))
144
+
145
+ export function getPose(id: string | undefined | null): Pose | undefined {
146
+ if (!id) return undefined
147
+ return poseById.get(id)
148
+ }
149
+
150
+ export function getPoseLabel(id: string | undefined | null, fallback?: string): string {
151
+ const p = getPose(id)
152
+ if (p) return p.label
153
+ if (fallback !== undefined) return fallback
154
+ return (id ?? "").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())
155
+ }
156
+
157
+ export function getPosePromptHint(id: string | undefined | null): string {
158
+ return getPose(id)?.promptHint ?? ""
159
+ }
160
+
161
+ export const POSE_IDS: ReadonlyArray<string> = POSES.map((p) => p.id)
162
+
163
+ export const POSE_CATEGORY_LABELS: Readonly<Record<PoseCategory, string>> = {
164
+ standing: "Standing",
165
+ seated: "Seated",
166
+ movement: "Movement",
167
+ action: "Action",
168
+ resting: "Resting",
169
+ "hand-position": "Hand Position",
170
+ "body-lean": "Body Lean",
171
+ "head-tilt": "Head Tilt",
172
+ activity: "Activity",
173
+ }
174
+
175
+ export const POSE_CATEGORY_ORDER: ReadonlyArray<PoseCategory> = [
176
+ "standing",
177
+ "seated",
178
+ "movement",
179
+ "action",
180
+ "resting",
181
+ "hand-position",
182
+ "body-lean",
183
+ "head-tilt",
184
+ "activity",
185
+ ]
186
+
187
+ /**
188
+ * Shape of Pose parameter data. Multi-dimensional: pose + optional
189
+ * orthogonal sub-pickers (hand position / body lean / head tilt) + optional
190
+ * pre/post free text.
191
+ */
192
+ export interface PoseValue {
193
+ pose?: string
194
+ /** Optional hand-position pose id (orthogonal to pose). */
195
+ handPosition?: string
196
+ /** Optional body-lean pose id (orthogonal to pose). */
197
+ bodyLean?: string
198
+ /** Optional head-tilt pose id (orthogonal to pose). */
199
+ headTilt?: string
200
+ /** Optional activity pose id — what the subject is DOING in the world
201
+ * (smoking, eating, texting, driving…). Distinct from posture / movement /
202
+ * action; orthogonal to the other sub-pickers. */
203
+ activity?: string
204
+ preText?: string
205
+ postText?: string
206
+ }
207
+
208
+ /**
209
+ * Build prompt hints from PoseData: optional pre-text, the selected pose's
210
+ * hint, the orthogonal hand-position / body-lean / head-tilt hints,
211
+ * optional post-text. Returns array — caller joins with ", ".
212
+ */
213
+ export function buildPoseHints(
214
+ data: Record<string, unknown> & PoseValue,
215
+ ): string[] {
216
+ const hints: string[] = []
217
+
218
+ const pre = typeof data.preText === "string" ? data.preText.trim() : ""
219
+ if (pre) hints.push(pre)
220
+
221
+ const poseId = typeof data.pose === "string" ? data.pose : ""
222
+ const poseHint = getPosePromptHint(poseId)
223
+ if (poseHint) hints.push(poseHint)
224
+
225
+ const handPositionId =
226
+ typeof data.handPosition === "string" ? data.handPosition : ""
227
+ const handPositionHint = getPosePromptHint(handPositionId)
228
+ if (handPositionHint) hints.push(handPositionHint)
229
+
230
+ const bodyLeanId = typeof data.bodyLean === "string" ? data.bodyLean : ""
231
+ const bodyLeanHint = getPosePromptHint(bodyLeanId)
232
+ if (bodyLeanHint) hints.push(bodyLeanHint)
233
+
234
+ const headTiltId = typeof data.headTilt === "string" ? data.headTilt : ""
235
+ const headTiltHint = getPosePromptHint(headTiltId)
236
+ if (headTiltHint) hints.push(headTiltHint)
237
+
238
+ const activityId = typeof data.activity === "string" ? data.activity : ""
239
+ const activityHint = getPosePromptHint(activityId)
240
+ if (activityHint) hints.push(activityHint)
241
+
242
+ const post = typeof data.postText === "string" ? data.postText.trim() : ""
243
+ if (post) hints.push(post)
244
+
245
+ return hints
246
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Canonical catalog of Post-Process Effects choices.
3
+ *
4
+ * Post-process effects describe an image-level grade or processing pass
5
+ * applied AFTER the image is captured / rendered — vignette, grain, halation,
6
+ * bloom, chromatic aberration, light leaks, scratches, soft-focus diffusion,
7
+ * dodge-and-burn, etc. These are the touches a colorist or photographer
8
+ * applies in the darkroom or grading suite.
9
+ *
10
+ * Distinct from:
11
+ * - Style — the artistic medium (oil paint, watercolor, anime).
12
+ * - Composition Effects — what the subject is made of or how it interacts
13
+ * with the frame (smoke sculpture, exploding particles, breaking the
14
+ * fourth wall).
15
+ * - Color Look — overall color grade direction (teal-orange, pastel, mono).
16
+ * - Atmosphere — what's in the air (fog, dust, rain).
17
+ *
18
+ * Single-pick — only one post-process pass is applied per consumer.
19
+ *
20
+ * Shared between picker UI and prompt-hint injection in the frontend DAG
21
+ * executor and the backend orchestrator.
22
+ */
23
+
24
+ export interface PostProcessEffect {
25
+ readonly id: string
26
+ readonly label: string
27
+ readonly description: string
28
+ readonly promptHint: string
29
+ }
30
+
31
+ export const POST_PROCESS_EFFECTS: ReadonlyArray<PostProcessEffect> = [
32
+ { id: "vignette-soft", label: "Soft Vignette", description: "Gentle corner darkening", promptHint: "soft dark vignette gently darkening the corners of the frame, drawing the eye toward the center without calling attention to itself" },
33
+ { id: "vignette-heavy", label: "Heavy Vignette", description: "Dramatic black corners", promptHint: "heavy black vignette circumscribing the subject in a dramatic dark oval, deep falloff at the edges that nearly crushes to black" },
34
+ { id: "dodge-and-burn", label: "Dodge & Burn", description: "Sculpted highlight/shadow", promptHint: "aggressive dodge-and-burn shaping with sculpted highlight and shadow chiaroscuro, hand-painted-style local contrast carving form into the subject" },
35
+ { id: "film-grain-fine", label: "Fine Film Grain", description: "Subtle 35mm-style grain", promptHint: "fine film grain overlay across the entire image, classic 35mm character with delicate organic noise that softens digital edges" },
36
+ { id: "film-grain-heavy", label: "Heavy Film Grain", description: "Coarse push-processed grain", promptHint: "heavy coarse grain like push-processed high-ISO film, gritty texture across midtones and dense noise in the shadows" },
37
+ { id: "halation-glow", label: "Halation Glow", description: "Cinestill red-halo bloom", promptHint: "warm halation glow blooming around bright highlights, signature cinestill-style red halo bleeding from light sources and tungsten practicals" },
38
+ { id: "bloom-glow", label: "Bloom Glow", description: "Romantic dreamy highlight bloom", promptHint: "dreamy soft-focus bloom on highlights, romantic glow that wraps around bright areas and lifts the overall mood with a hazy halo" },
39
+ { id: "chromatic-aberration", label: "Chromatic Aberration", description: "Red/cyan fringe on edges", promptHint: "visible chromatic aberration with red and cyan fringing on contrast edges, lens-imperfection look that reads as authentic optical character" },
40
+ { id: "light-leak", label: "Light Leak", description: "Warm streak across the frame", promptHint: "warm orange light leak streaking across one edge of the frame, fogged film aesthetic with a soft amber wash bleeding into the image" },
41
+ { id: "film-burn", label: "Film Burn", description: "Vintage Super-8 corner flare", promptHint: "film burn flare in the corner with a vintage Super-8 aesthetic, charred edge bleeding orange and white light into the frame" },
42
+ { id: "scratched-emulsion", label: "Scratched Emulsion", description: "Aged film scratches + dust", promptHint: "vertical scratches and dust marks like aged film emulsion, hairline streaks running through the frame and scattered dust specks across the image" },
43
+ { id: "color-fringe", label: "Color Fringe", description: "Subtle high-contrast fringing", promptHint: "subtle color fringing on high-contrast edges, gentle prismatic shimmer that adds optical authenticity without overwhelming the image" },
44
+ { id: "soft-focus-diffusion", label: "Soft-Focus Diffusion", description: "Hazy dreamy highlight bloom", promptHint: "soft-focus diffusion filter — slightly hazy dreamy bloom on highlights, glowing skin tones and a romantic veil across the entire image" },
45
+ { id: "contrast-boost", label: "Contrast Boost", description: "Crushed shadows + pushed highlights", promptHint: "aggressive contrast boost, deepened shadows crushing toward black and pushed highlights, punchy editorial-grade tonal separation" },
46
+ { id: "sharpening", label: "Heavy Sharpening", description: "Aggressive edge-sharpening pass, crisp micro-detail", promptHint: "aggressive edge-sharpening pass with crisp micro-detail emphasized, hardened edges and pronounced texture acutance across the entire image" },
47
+ { id: "clarity-boost", label: "Clarity Boost", description: "Mid-tone clarity enhancement, increased local contrast", promptHint: "mid-tone clarity enhancement with increased local contrast, deepened textural definition and punchy structural detail without crushing shadows or highlights" },
48
+ { id: "dehaze", label: "Dehaze", description: "Atmospheric dehaze applied, removing softness and lifting contrast through fog", promptHint: "atmospheric dehaze applied across the frame, removing veiling softness and lifting contrast through haze and fog with restored color saturation in distant tones" },
49
+ { id: "lift-gamma-gain", label: "Lift-Gamma-Gain Grade", description: "Three-way color grading wheels — shadow lift, midtone gamma, highlight gain", promptHint: "three-way color grading with lift-gamma-gain wheels shaping shadow lift, midtone gamma and highlight gain independently, professional colorist-grade tonal sculpting" },
50
+ ] as const
51
+
52
+ const postProcessById = new Map<string, PostProcessEffect>(
53
+ POST_PROCESS_EFFECTS.map((p) => [p.id, p]),
54
+ )
55
+
56
+ export function getPostProcessEffect(id: string | undefined | null): PostProcessEffect | undefined {
57
+ if (!id) return undefined
58
+ return postProcessById.get(id)
59
+ }
60
+
61
+ export function getPostProcessEffectLabel(id: string | undefined | null, fallback?: string): string {
62
+ const p = getPostProcessEffect(id)
63
+ if (p) return p.label
64
+ if (fallback !== undefined) return fallback
65
+ return (id ?? "").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())
66
+ }
67
+
68
+ export function getPostProcessEffectPromptHint(id: string | undefined | null): string {
69
+ return getPostProcessEffect(id)?.promptHint ?? ""
70
+ }
71
+
72
+ /**
73
+ * Multi-pick: 1-2 post-process effect ids → composite grading clause.
74
+ * Common pairs: vignette + film-grain, halation + bloom, dodge-burn +
75
+ * chromatic-aberration. Each entry already describes a complete grading
76
+ * pass, so we emit independently and let the comma-join compose them.
77
+ */
78
+ export function buildPostProcessHints(value: unknown): string[] {
79
+ const ids: string[] = []
80
+ if (typeof value === "string" && value) ids.push(value)
81
+ else if (Array.isArray(value)) {
82
+ for (const v of value) {
83
+ if (typeof v === "string" && v && !ids.includes(v)) ids.push(v)
84
+ }
85
+ }
86
+ const out: string[] = []
87
+ for (const id of ids) {
88
+ const hint = getPostProcessEffectPromptHint(id)
89
+ if (hint) out.push(hint)
90
+ }
91
+ return out
92
+ }
93
+
94
+ export const POST_PROCESS_EFFECT_IDS: ReadonlyArray<string> = POST_PROCESS_EFFECTS.map((p) => p.id)
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Path-1 structured prompt fields → composed prompt fragment.
3
+ *
4
+ * Used by:
5
+ * - Nodaro's MCP server (Phase 6 v1.1) — generate_image / generate_video verbs
6
+ * accept these structured fields and run them through this helper before
7
+ * passing the composite prompt to the underlying route.
8
+ * - (Optional) Frontend editor — same logic so the editor and MCP produce
9
+ * identical prompts for the same inputs.
10
+ *
11
+ * NOTE: this is a parallel utility to `prompt-builder.ts`. The latter does
12
+ * image-specific composition (character/template/reference handling), while
13
+ * this module handles the Path-1 structured-field shape that MCP verbs accept
14
+ * (free-form strings under person/styling/setting/camera/lens/mood). Existing
15
+ * frontend `buildPersonHints` / `buildStylingHints` helpers operate on catalog
16
+ * IDs and are not interchangeable with this renderer.
17
+ */
18
+
19
+ export interface StructuredPromptFields {
20
+ person?: {
21
+ age?: number
22
+ gender?: "man" | "woman" | "child" | "non-binary"
23
+ hair?: string
24
+ eyes?: string
25
+ expression?: string
26
+ profession?: string
27
+ warriorType?: string
28
+ }
29
+ styling?: {
30
+ mood?: string
31
+ lighting?: string
32
+ aesthetic?: string
33
+ colorLook?: string
34
+ }
35
+ setting?: {
36
+ era?: string
37
+ atmosphere?: string
38
+ backdrop?: string
39
+ }
40
+ camera?: {
41
+ framing?: string
42
+ motion?: string
43
+ format?: string
44
+ }
45
+ lens?: {
46
+ focalLength?: string
47
+ aperture?: string
48
+ }
49
+ /** Standalone shorthand mood (overrides styling.mood if both are set). */
50
+ mood?: string
51
+ }
52
+
53
+ /**
54
+ * Render structured fields → composite prompt fragment to be appended to the
55
+ * user's free-text prompt. Returns "" if no fields populated.
56
+ */
57
+ export function renderStructuredFields(fields: StructuredPromptFields): string {
58
+ const parts: string[] = []
59
+ if (fields.person) parts.push(renderPerson(fields.person))
60
+ if (fields.styling) {
61
+ // The standalone `mood` shorthand overrides styling.mood (per the type
62
+ // doc) — drop styling.mood when the shorthand is set so the two don't both
63
+ // render conflicting "mood …" / "Mood: …" directives.
64
+ const styling = fields.mood ? { ...fields.styling, mood: undefined } : fields.styling
65
+ parts.push(renderStyling(styling))
66
+ }
67
+ if (fields.setting) parts.push(renderSetting(fields.setting))
68
+ if (fields.camera) parts.push(renderCamera(fields.camera))
69
+ if (fields.lens) parts.push(renderLens(fields.lens))
70
+ if (fields.mood) parts.push(`Mood: ${fields.mood}.`)
71
+ return parts.filter((s) => s.length > 0).join(" ")
72
+ }
73
+
74
+ function renderPerson(p: NonNullable<StructuredPromptFields["person"]>): string {
75
+ const bits: string[] = []
76
+ if (p.age) bits.push(`${p.age} years old`)
77
+ if (p.gender) bits.push(p.gender)
78
+ if (p.profession) bits.push(p.profession)
79
+ if (p.warriorType) bits.push(`(${p.warriorType})`)
80
+ if (p.hair) bits.push(`with ${p.hair} hair`)
81
+ if (p.eyes) bits.push(`${p.eyes} eyes`)
82
+ if (p.expression) bits.push(`${p.expression} expression`)
83
+ return bits.length > 0 ? `Subject: ${bits.join(", ")}.` : ""
84
+ }
85
+
86
+ function renderStyling(s: NonNullable<StructuredPromptFields["styling"]>): string {
87
+ const bits: string[] = []
88
+ if (s.mood) bits.push(`mood ${s.mood}`)
89
+ if (s.lighting) bits.push(`${s.lighting} lighting`)
90
+ if (s.aesthetic) bits.push(`${s.aesthetic} aesthetic`)
91
+ if (s.colorLook) bits.push(`${s.colorLook} color`)
92
+ return bits.length > 0 ? `Style: ${bits.join(", ")}.` : ""
93
+ }
94
+
95
+ function renderSetting(s: NonNullable<StructuredPromptFields["setting"]>): string {
96
+ const bits: string[] = []
97
+ if (s.era) bits.push(`${s.era} era`)
98
+ if (s.atmosphere) bits.push(`${s.atmosphere} atmosphere`)
99
+ if (s.backdrop) bits.push(`set in ${s.backdrop}`)
100
+ return bits.length > 0 ? `Setting: ${bits.join(", ")}.` : ""
101
+ }
102
+
103
+ function renderCamera(c: NonNullable<StructuredPromptFields["camera"]>): string {
104
+ const bits: string[] = []
105
+ if (c.framing) bits.push(`${c.framing} framing`)
106
+ if (c.motion) bits.push(c.motion)
107
+ if (c.format) bits.push(`${c.format} format`)
108
+ return bits.length > 0 ? `Camera: ${bits.join(", ")}.` : ""
109
+ }
110
+
111
+ function renderLens(l: NonNullable<StructuredPromptFields["lens"]>): string {
112
+ const bits: string[] = []
113
+ if (l.focalLength) bits.push(l.focalLength)
114
+ if (l.aperture) bits.push(`f/${l.aperture}`)
115
+ return bits.length > 0 ? `Lens: ${bits.join(", ")}.` : ""
116
+ }