@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
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Music mood catalog: energy + emotion + vibe sub-fields.
3
+ * Composed by buildMusicMoodHints into "[energy] [emotion] [vibe]".
4
+ */
5
+
6
+ import { pickIds } from "@nodaro/shared"
7
+
8
+ export interface MusicMoodEntry {
9
+ readonly id: string
10
+ readonly label: string
11
+ readonly description: string
12
+ readonly promptHint: string
13
+ }
14
+
15
+ export const MUSIC_ENERGIES: ReadonlyArray<MusicMoodEntry> = [
16
+ { id: "low", label: "Low", description: "Calm, slow", promptHint: "low-energy" },
17
+ { id: "mellow", label: "Mellow", description: "Relaxed, laid-back", promptHint: "mellow" },
18
+ { id: "gentle", label: "Gentle", description: "Soft, tender", promptHint: "gentle" },
19
+ { id: "moderate", label: "Moderate", description: "Balanced, steady", promptHint: "moderate-energy" },
20
+ { id: "building", label: "Building", description: "Slowly intensifying", promptHint: "building energy" },
21
+ { id: "upbeat", label: "Upbeat", description: "Lively, optimistic", promptHint: "upbeat" },
22
+ { id: "driving", label: "Driving", description: "Forward-pushing momentum", promptHint: "driving" },
23
+ { id: "high", label: "High", description: "Energetic, intense", promptHint: "high-energy" },
24
+ { id: "explosive", label: "Explosive", description: "Bursting, wild", promptHint: "explosive" },
25
+ { id: "frenetic", label: "Frenetic", description: "Intense, rapid", promptHint: "frenetic" },
26
+ { id: "pulsing", label: "Pulsing", description: "Rhythmic, beat-driven", promptHint: "pulsing" },
27
+ { id: "throbbing", label: "Throbbing", description: "Heavy, hypnotic", promptHint: "throbbing" },
28
+ { id: "simmering", label: "Simmering", description: "Tense, restrained", promptHint: "simmering" },
29
+ { id: "ferocious", label: "Ferocious", description: "Aggressive, relentless", promptHint: "ferocious" },
30
+ ] as const
31
+
32
+ export const MUSIC_EMOTIONS: ReadonlyArray<MusicMoodEntry> = [
33
+ { id: "happy", label: "Happy", description: "Joyful, bright", promptHint: "happy" },
34
+ { id: "joyful", label: "Joyful", description: "Exuberant, celebratory", promptHint: "joyful" },
35
+ { id: "euphoric", label: "Euphoric", description: "Ecstatic, transcendent", promptHint: "euphoric" },
36
+ { id: "melancholic", label: "Melancholic", description: "Wistful, bittersweet", promptHint: "melancholic" },
37
+ { id: "sad", label: "Sad", description: "Somber, sorrowful", promptHint: "sad" },
38
+ { id: "longing", label: "Longing", description: "Yearning, aching", promptHint: "longing" },
39
+ { id: "lonely", label: "Lonely", description: "Isolated, distant", promptHint: "lonely" },
40
+ { id: "angry", label: "Angry", description: "Aggressive, hostile", promptHint: "angry" },
41
+ { id: "defiant", label: "Defiant", description: "Rebellious, resistant", promptHint: "defiant" },
42
+ { id: "triumphant", label: "Triumphant", description: "Heroic, victorious", promptHint: "triumphant" },
43
+ { id: "victorious", label: "Victorious", description: "Conquering, anthemic", promptHint: "victorious" },
44
+ { id: "tender", label: "Tender", description: "Soft, affectionate", promptHint: "tender" },
45
+ { id: "romantic", label: "Romantic", description: "Loving, sensual", promptHint: "romantic" },
46
+ { id: "haunting", label: "Haunting", description: "Eerie, lingering", promptHint: "haunting" },
47
+ { id: "mysterious", label: "Mysterious", description: "Enigmatic, secretive", promptHint: "mysterious" },
48
+ { id: "menacing", label: "Menacing", description: "Threatening, foreboding", promptHint: "menacing" },
49
+ { id: "playful", label: "Playful", description: "Whimsical, fun", promptHint: "playful" },
50
+ { id: "mischievous", label: "Mischievous", description: "Sly, troublemaker", promptHint: "mischievous" },
51
+ { id: "anxious", label: "Anxious", description: "Tense, apprehensive", promptHint: "anxious" },
52
+ { id: "fearful", label: "Fearful", description: "Afraid, panicked", promptHint: "fearful" },
53
+ { id: "hopeful", label: "Hopeful", description: "Uplifting, aspirational", promptHint: "hopeful" },
54
+ { id: "inspirational",label: "Inspirational",description: "Motivational, soaring", promptHint: "inspirational" },
55
+ { id: "nostalgic", label: "Nostalgic", description: "Backward-looking, wistful", promptHint: "nostalgic" },
56
+ { id: "bittersweet", label: "Bittersweet", description: "Sweet sorrow, mixed", promptHint: "bittersweet" },
57
+ { id: "peaceful", label: "Peaceful", description: "Serene, restful", promptHint: "peaceful" },
58
+ { id: "contemplative",label: "Contemplative",description: "Reflective, thoughtful", promptHint: "contemplative" },
59
+ { id: "ethereal", label: "Ethereal", description: "Otherworldly, floating", promptHint: "ethereal" },
60
+ { id: "awe", label: "Awe", description: "Wonder, vastness", promptHint: "awe-inspiring" },
61
+ ] as const
62
+
63
+ export const MUSIC_VIBES: ReadonlyArray<MusicMoodEntry> = [
64
+ { id: "cinematic", label: "Cinematic", description: "Score-like, evocative", promptHint: "cinematic" },
65
+ { id: "intimate", label: "Intimate", description: "Personal, close", promptHint: "intimate" },
66
+ { id: "epic", label: "Epic", description: "Grand, sweeping", promptHint: "epic" },
67
+ { id: "anthemic", label: "Anthemic", description: "Crowd-singing big", promptHint: "anthemic" },
68
+ { id: "lo-fi", label: "Lo-fi", description: "Warm, imperfect", promptHint: "lo-fi" },
69
+ { id: "polished", label: "Polished", description: "Pristine, mainstream", promptHint: "polished" },
70
+ { id: "raw", label: "Raw", description: "Unfiltered, gritty", promptHint: "raw" },
71
+ { id: "dreamy", label: "Dreamy", description: "Hazy, surreal", promptHint: "dreamy" },
72
+ { id: "hypnotic", label: "Hypnotic", description: "Looped, trance-like", promptHint: "hypnotic" },
73
+ { id: "dark", label: "Dark", description: "Brooding, shadowy", promptHint: "dark" },
74
+ { id: "gritty", label: "Gritty", description: "Coarse, urban", promptHint: "gritty" },
75
+ { id: "uplifting", label: "Uplifting", description: "Bright, encouraging", promptHint: "uplifting" },
76
+ { id: "tense", label: "Tense", description: "Suspenseful", promptHint: "tense" },
77
+ { id: "spacey", label: "Spacey", description: "Cosmic, vast", promptHint: "spacey" },
78
+ { id: "psychedelic", label: "Psychedelic", description: "Mind-bending, swirling", promptHint: "psychedelic" },
79
+ { id: "noir", label: "Noir", description: "Smoky, hard-boiled", promptHint: "noir" },
80
+ { id: "vintage", label: "Vintage", description: "Retro, period-aged", promptHint: "vintage" },
81
+ { id: "futuristic", label: "Futuristic", description: "Sci-fi, forward-looking", promptHint: "futuristic" },
82
+ { id: "suspenseful", label: "Suspenseful", description: "Dread, edge-of-seat", promptHint: "suspenseful" },
83
+ { id: "espionage", label: "Espionage", description: "Spy-thriller, covert ops", promptHint: "espionage" },
84
+ { id: "cold", label: "Cold", description: "Icy, detached, stark", promptHint: "cold" },
85
+ { id: "clandestine", label: "Clandestine", description: "Secret, shadowy, covert", promptHint: "clandestine" },
86
+ ] as const
87
+
88
+ const ENERGY_BY_ID = new Map(MUSIC_ENERGIES.map((x) => [x.id, x]))
89
+ const EMOTION_BY_ID = new Map(MUSIC_EMOTIONS.map((x) => [x.id, x]))
90
+ const VIBE_BY_ID = new Map(MUSIC_VIBES.map((x) => [x.id, x]))
91
+
92
+ export function getMusicEnergy(id: string | undefined): MusicMoodEntry | undefined {
93
+ return id ? ENERGY_BY_ID.get(id) : undefined
94
+ }
95
+ export function getMusicEmotion(id: string | undefined): MusicMoodEntry | undefined {
96
+ return id ? EMOTION_BY_ID.get(id) : undefined
97
+ }
98
+ export function getMusicVibe(id: string | undefined): MusicMoodEntry | undefined {
99
+ return id ? VIBE_BY_ID.get(id) : undefined
100
+ }
101
+
102
+ export function buildMusicMoodHints(data: {
103
+ readonly preText?: string
104
+ readonly postText?: string
105
+ readonly energy?: string
106
+ readonly emotion?: string | ReadonlyArray<string>
107
+ readonly vibe?: string | ReadonlyArray<string>
108
+ }): string {
109
+ const fragments: string[] = []
110
+ const pre = typeof data.preText === "string" ? data.preText.trim() : ""
111
+ if (pre) fragments.push(pre)
112
+
113
+ const parts: string[] = []
114
+ const e = getMusicEnergy(data.energy)
115
+ if (e) parts.push(e.promptHint)
116
+
117
+ const emotionHints = pickIds(data.emotion)
118
+ .map((id) => getMusicEmotion(id)?.promptHint)
119
+ .filter((h): h is string => !!h)
120
+ if (emotionHints.length > 0) parts.push(emotionHints.join(", "))
121
+
122
+ const vibeHints = pickIds(data.vibe)
123
+ .map((id) => getMusicVibe(id)?.promptHint)
124
+ .filter((h): h is string => !!h)
125
+ if (vibeHints.length > 0) parts.push(vibeHints.join(", "))
126
+
127
+ if (parts.length > 0) fragments.push(parts.join(" "))
128
+
129
+ const post = typeof data.postText === "string" ? data.postText.trim() : ""
130
+ if (post) fragments.push(post)
131
+
132
+ return fragments.join(", ")
133
+ }
134
+
135
+ export const MUSIC_MOOD_DEFAULT_DATA: {
136
+ preText?: string; postText?: string; energy?: string; emotion?: string | ReadonlyArray<string>; vibe?: string | ReadonlyArray<string>
137
+ } = {}
@@ -0,0 +1,81 @@
1
+ // Object Studio asset presets — SINGLE SOURCE OF TRUTH for the Angles /
2
+ // Materials / Variations preset chips AND their generation prompt fragments.
3
+ //
4
+ // Both the frontend tab wrappers (`object-studio/{angles,materials,variations}-tab.tsx`)
5
+ // and the backend route (`generate-object-asset.ts` — VARIANTS validation +
6
+ // buildVariantPrompt) import from here, so the preset list can never drift
7
+ // between client and server.
8
+ //
9
+ // Why this file exists: the frontend shipped 9 / 13 / 11 presets while the
10
+ // backend enum only accepted 5 / 6 / 5, so ~17 chips 400'd with a silent
11
+ // failure (the route rejects any variant not in VARIANTS, and the studio sent
12
+ // them as assetType:"angles|materials|variations", NOT "custom"). Deriving both
13
+ // sides from one constant is the drift-proof fix.
14
+ //
15
+ // To add a preset: add ONE entry to the matching prompt map below. The key list
16
+ // and the backend validation update automatically.
17
+
18
+ /** Angle preset → prompt fragment (slotted into "<name>, <fragment>. <base>"). */
19
+ export const OBJECT_ANGLE_PROMPTS: Record<string, string> = {
20
+ front: "front view, facing camera directly",
21
+ side: "side profile view",
22
+ top: "top-down view, bird's eye perspective",
23
+ back: "back view, rear perspective",
24
+ "three-quarter": "three-quarter angle view, dynamic perspective",
25
+ detail: "extreme close-up detail shot, macro view of the surface and craftsmanship",
26
+ "in-context": "shown in its natural environment and use context, real-world setting",
27
+ exploded: "exploded technical view, components separated and floating apart",
28
+ perspective: "dramatic low-angle perspective view, strong sense of depth",
29
+ }
30
+
31
+ /** Material preset → prompt fragment. */
32
+ export const OBJECT_MATERIAL_PROMPTS: Record<string, string> = {
33
+ wood: "made of polished wood, wood grain texture visible",
34
+ metal: "made of brushed metal, metallic surface with subtle reflections",
35
+ glass: "made of transparent glass, see-through with subtle reflections",
36
+ plastic: "made of smooth plastic, matte finish",
37
+ fabric: "covered in soft fabric texture, textile material",
38
+ stone: "carved from stone, rough granite or marble texture",
39
+ ceramic: "made of glazed ceramic, smooth glossy fired-clay surface",
40
+ leather: "wrapped in tanned leather, natural grain and visible stitching",
41
+ paper: "made of folded paper and cardboard, matte fibrous texture",
42
+ gold: "made of polished gold, warm reflective precious-metal sheen",
43
+ silver: "made of polished silver, cool bright reflective metal",
44
+ copper: "made of copper, warm reddish metal with a subtle patina",
45
+ marble: "carved from marble, smooth polished stone with natural veining",
46
+ }
47
+
48
+ /** Variation preset → prompt fragment. */
49
+ export const OBJECT_VARIATION_PROMPTS: Record<string, string> = {
50
+ clean: "brand new pristine condition, perfect and clean",
51
+ weathered: "slightly weathered and aged, with wear marks",
52
+ damaged: "battle-damaged with scratches and dents",
53
+ ornate: "ornately decorated with intricate details and patterns",
54
+ minimal: "minimalist design, clean simple lines",
55
+ broken: "broken and cracked, shattered with missing pieces",
56
+ antique: "antique vintage version, aged patina and period detailing",
57
+ futuristic: "sleek futuristic sci-fi redesign, advanced materials with a subtle glow",
58
+ holographic: "iridescent holographic finish, rainbow refractive sheen",
59
+ dirty: "dirty and grimy, covered in dust, mud and stains",
60
+ polished: "immaculately polished, mirror-bright showroom finish",
61
+ }
62
+
63
+ export const OBJECT_ANGLE_PRESETS: readonly string[] = Object.keys(OBJECT_ANGLE_PROMPTS)
64
+ export const OBJECT_MATERIAL_PRESETS: readonly string[] = Object.keys(OBJECT_MATERIAL_PROMPTS)
65
+ export const OBJECT_VARIATION_PRESETS: readonly string[] = Object.keys(OBJECT_VARIATION_PROMPTS)
66
+
67
+ export type ObjectPresetAssetType = "angles" | "materials" | "variations"
68
+
69
+ /** Preset key lists per asset type — backend VARIANTS validation reads this. */
70
+ export const OBJECT_ASSET_PRESETS: Record<ObjectPresetAssetType, readonly string[]> = {
71
+ angles: OBJECT_ANGLE_PRESETS,
72
+ materials: OBJECT_MATERIAL_PRESETS,
73
+ variations: OBJECT_VARIATION_PRESETS,
74
+ }
75
+
76
+ /** Prompt-fragment maps per asset type — backend buildVariantPrompt reads this. */
77
+ export const OBJECT_ASSET_PROMPTS: Record<ObjectPresetAssetType, Record<string, string>> = {
78
+ angles: OBJECT_ANGLE_PROMPTS,
79
+ materials: OBJECT_MATERIAL_PROMPTS,
80
+ variations: OBJECT_VARIATION_PROMPTS,
81
+ }
@@ -0,0 +1,281 @@
1
+ import type { HintNodeLike, HintEdgeLike, HintGraphContext } from "@nodaro/shared"
2
+ export type { HintNodeLike, HintEdgeLike, HintGraphContext }
3
+ /**
4
+ * Single source of truth for parameter-node prompt-hint text.
5
+ *
6
+ * Every parameter node (framing, lens, camera-format, lighting, color-look,
7
+ * atmosphere, style, setting, person, mood, pose, styling, temporal, tone,
8
+ * text-prompt, camera-motion) dispatches through `getParameterPromptHint` to
9
+ * produce the descriptive clause it contributes to a consumer's prompt.
10
+ *
11
+ * The frontend DAG executor and backend workflow orchestrator both call this,
12
+ * so the text injected via the `cinematography` handle is identical to the
13
+ * text injected when the node is wired directly into a Text Prompt / LLM Chat
14
+ * / Combine Text input.
15
+ *
16
+ * Camera-motion is graph-aware: when `ctx` is provided the function walks the
17
+ * node's `startState`/`endState` incoming edges and composes a fully formed
18
+ * sentence via `composeCameraMotionHintFromConnections`. Without `ctx` only
19
+ * the bare motion description is returned.
20
+ */
21
+
22
+ import { buildFramingHints } from "./framing.js"
23
+ import { buildLightingHints } from "./lighting.js"
24
+ import { getLensPromptHint } from "./lens.js"
25
+ import { getCameraFormatPromptHint } from "./camera-format.js"
26
+ import { getColorLookPromptHint } from "./color-look.js"
27
+ import { buildAtmosphereHints } from "./atmosphere.js"
28
+ import { buildActionFxHints } from "./action-fx.js"
29
+ import { getStylePromptHint } from "./style.js"
30
+ import { getSettingPromptHint } from "./setting.js"
31
+ import { getLoopSubjectPromptHint } from "./loop-subject.js"
32
+ import { buildPersonHints } from "./person.js"
33
+ import { buildMoodHints } from "./mood.js"
34
+ import { buildPoseHints } from "./pose.js"
35
+ import { buildStylingHints } from "./styling.js"
36
+ import { buildTemporalHints } from "./temporal.js"
37
+ import { composeCameraMotionHintFromConnections } from "./camera-motions.js"
38
+ import { composeTransitionHintFromConnections, type TransitionDuration, type TransitionIntensity, type TransitionPosition, type TransitionTiming } from "./transitions.js"
39
+ import { composeCharacterFxHintFromConnections, type CharacterFxDuration, type CharacterFxIntensity, type CharacterFxPosition, type CharacterFxTiming } from "./character-fx.js"
40
+ import { buildMaterialHints } from "./materials.js"
41
+ import { getAnimal } from "@nodaro/shared"
42
+ import { getVehicle } from "@nodaro/shared"
43
+ import { getWeapon } from "@nodaro/shared"
44
+ import { getFurniture } from "@nodaro/shared"
45
+ import { getPhotoGenrePromptHint } from "./photo-genre.js"
46
+ import { getBackdropPromptHint } from "./backdrop.js"
47
+ import { buildHeldPropHints } from "./held-prop.js"
48
+ import { buildPhotographerHints } from "./photographer.js"
49
+ import { buildAestheticHints } from "./aesthetic.js"
50
+ import { getEraPromptHint } from "./era.js"
51
+ import { buildExposureHints } from "./exposure-settings.js"
52
+ import { getRenderQualityPromptHint } from "./render-quality.js"
53
+ import { getCompositionEffectPromptHint } from "./composition-effects.js"
54
+ import { buildPostProcessHints } from "./post-process-effects.js"
55
+ import { buildMusicGenreHints } from "./music-genre.js"
56
+ import { buildMusicMoodHints } from "./music-mood.js"
57
+ import { buildInstrumentationHints } from "./instrumentation.js"
58
+ import { buildVoiceCharacterHints } from "./voice-character.js"
59
+ import { buildVoiceDeliveryHints } from "./voice-delivery.js"
60
+
61
+
62
+ function asStr(v: unknown): string {
63
+ return typeof v === "string" ? v : ""
64
+ }
65
+
66
+ /** Extract the display name from a character / face / object / location ref node. */
67
+ function extractCharacterRefName(node: HintNodeLike): string | undefined {
68
+ const d = (node.data ?? {}) as Record<string, unknown>
69
+ const candidates = [d.characterName, d.faceName, d.objectName, d.locationName]
70
+ for (const v of candidates) {
71
+ if (typeof v === "string" && v.trim().length > 0) return v.trim()
72
+ }
73
+ return undefined
74
+ }
75
+
76
+ /** Compose `[preText, mainHint, postText]` into a comma-joined string,
77
+ * honoring the user's free-text fragments around the structured hint.
78
+ * Helpers like `getStylePromptHint` that already include preText/postText
79
+ * composition (build*Hints in mood/person/etc.) bypass this — they
80
+ * return the fully-composed string directly. */
81
+ function withCustomText(data: Record<string, unknown>, mainHint: string): string {
82
+ const fragments: string[] = []
83
+ const pre = typeof data.preText === "string" ? data.preText.trim() : ""
84
+ if (pre) fragments.push(pre)
85
+ if (mainHint) fragments.push(mainHint)
86
+ const post = typeof data.postText === "string" ? data.postText.trim() : ""
87
+ if (post) fragments.push(post)
88
+ return fragments.join(", ")
89
+ }
90
+
91
+ /**
92
+ * Dispatch by parameter-node type to its prompt-hint string. For camera-motion,
93
+ * pass `ctx` to include the composed start/end clauses; otherwise only the
94
+ * bare motion description is returned.
95
+ */
96
+ export function getParameterPromptHint(
97
+ node: HintNodeLike | undefined,
98
+ ctx?: HintGraphContext,
99
+ ): string {
100
+ if (!node?.type) return ""
101
+ const data = (node.data ?? {}) as Record<string, unknown>
102
+
103
+ if (node.type === "camera-motion") {
104
+ const motionId = asStr(data.cameraMotion) || undefined
105
+ if (!ctx) return withCustomText(data, composeCameraMotionHintFromConnections(motionId, [], []))
106
+ const startHints: string[] = []
107
+ const endHints: string[] = []
108
+ for (const edge of ctx.edges) {
109
+ if (edge.target !== node.id) continue
110
+ const src = ctx.nodes.find((n) => n.id === edge.source)
111
+ if (!src) continue
112
+ // Pass no ctx for nested resolution: startState/endState inputs are
113
+ // themselves parameter nodes (framing/tone/etc.) that don't need graph
114
+ // context, and avoiding recursion keeps the walk cycle-safe.
115
+ const hint = getParameterPromptHint(src)
116
+ if (!hint) continue
117
+ if (edge.targetHandle === "startState") startHints.push(hint)
118
+ else if (edge.targetHandle === "endState") endHints.push(hint)
119
+ }
120
+ return withCustomText(data, composeCameraMotionHintFromConnections(motionId, startHints, endHints))
121
+ }
122
+
123
+ if (node.type === "transition") {
124
+ const raw = data.transition
125
+ const transitionId: string | string[] | undefined =
126
+ Array.isArray(raw)
127
+ ? raw.filter((s): s is string => typeof s === "string" && s.length > 0)
128
+ : (asStr(raw) || undefined)
129
+ const timing: TransitionTiming = {
130
+ position: asStr(data.position) as TransitionPosition | undefined,
131
+ duration: asStr(data.duration) as TransitionDuration | undefined,
132
+ intensity: asStr(data.intensity) as TransitionIntensity | undefined,
133
+ }
134
+ if (!ctx) {
135
+ return withCustomText(data, composeTransitionHintFromConnections(transitionId, [], [], timing))
136
+ }
137
+ const startHints: string[] = []
138
+ const endHints: string[] = []
139
+ for (const edge of ctx.edges) {
140
+ if (edge.target !== node.id) continue
141
+ const src = ctx.nodes.find((n) => n.id === edge.source)
142
+ if (!src) continue
143
+ const hint = getParameterPromptHint(src) // no ctx — cycle-safe
144
+ if (!hint) continue
145
+ if (edge.targetHandle === "startState") startHints.push(hint)
146
+ else if (edge.targetHandle === "endState") endHints.push(hint)
147
+ }
148
+ return withCustomText(data, composeTransitionHintFromConnections(transitionId, startHints, endHints, timing))
149
+ }
150
+
151
+ if (node.type === "character-fx") {
152
+ const raw = data.characterFx
153
+ const effectId: string | string[] | undefined =
154
+ Array.isArray(raw)
155
+ ? raw.filter((s): s is string => typeof s === "string" && s.length > 0)
156
+ : (asStr(raw) || undefined)
157
+ const timing: CharacterFxTiming = {
158
+ position: asStr(data.position) as CharacterFxPosition | undefined,
159
+ duration: asStr(data.duration) as CharacterFxDuration | undefined,
160
+ intensity: asStr(data.intensity) as CharacterFxIntensity | undefined,
161
+ }
162
+ if (!ctx) {
163
+ return withCustomText(data, composeCharacterFxHintFromConnections(effectId, [], timing))
164
+ }
165
+ const targetNames: string[] = []
166
+ for (const edge of ctx.edges) {
167
+ if (edge.target !== node.id) continue
168
+ if (edge.targetHandle !== "target") continue
169
+ const src = ctx.nodes.find((n) => n.id === edge.source)
170
+ if (!src) continue
171
+ const name = extractCharacterRefName(src)
172
+ if (name) targetNames.push(name)
173
+ }
174
+ return withCustomText(data, composeCharacterFxHintFromConnections(effectId, targetNames, timing))
175
+ }
176
+
177
+ switch (node.type) {
178
+ case "framing":
179
+ return withCustomText(data, buildFramingHints(data).join(", "))
180
+ case "lighting":
181
+ return withCustomText(data, buildLightingHints(data).join(", "))
182
+ case "lens":
183
+ return withCustomText(data, getLensPromptHint(asStr(data.lens)))
184
+ case "camera-format":
185
+ return withCustomText(data, getCameraFormatPromptHint(asStr(data.cameraFormat)))
186
+ case "color-look":
187
+ return withCustomText(data, getColorLookPromptHint(asStr(data.colorLook)))
188
+
189
+ // build*Hints in music-* / voice-* / mood / person / etc. compose
190
+ // preText/postText internally — bypass the wrapper to avoid double-
191
+ // composition.
192
+ case "music-genre":
193
+ return buildMusicGenreHints((data ?? {}) as Parameters<typeof buildMusicGenreHints>[0])
194
+ case "music-mood":
195
+ return buildMusicMoodHints((data ?? {}) as Parameters<typeof buildMusicMoodHints>[0])
196
+ case "instrumentation":
197
+ return buildInstrumentationHints((data ?? {}) as Parameters<typeof buildInstrumentationHints>[0])
198
+ case "voice-character":
199
+ return buildVoiceCharacterHints((data ?? {}) as Parameters<typeof buildVoiceCharacterHints>[0])
200
+ case "voice-delivery":
201
+ return buildVoiceDeliveryHints((data ?? {}) as Parameters<typeof buildVoiceDeliveryHints>[0])
202
+ case "person":
203
+ return buildPersonHints(data).join(", ")
204
+ case "mood":
205
+ return buildMoodHints(data).join(", ")
206
+ case "pose":
207
+ return buildPoseHints(data).join(", ")
208
+ case "styling":
209
+ return buildStylingHints(data).join(", ")
210
+
211
+ case "atmosphere":
212
+ return withCustomText(data, buildAtmosphereHints(data.atmosphere).join(", "))
213
+ case "action-fx":
214
+ return withCustomText(data, buildActionFxHints(data.actionFx).join(", "))
215
+ case "style":
216
+ return withCustomText(data, getStylePromptHint(asStr(data.style)))
217
+ case "setting":
218
+ return withCustomText(data, getSettingPromptHint(asStr(data.setting)))
219
+ case "loop-subject":
220
+ return withCustomText(data, getLoopSubjectPromptHint(asStr(data.loopSubject)))
221
+ case "material":
222
+ return withCustomText(data, buildMaterialHints(data.material))
223
+ case "animal": {
224
+ const animal = getAnimal(asStr(data.animal))
225
+ return withCustomText(
226
+ data,
227
+ animal ? `featuring a ${animal.label.toLowerCase()}, ${animal.description}` : "",
228
+ )
229
+ }
230
+ case "vehicle": {
231
+ const vehicle = getVehicle(asStr(data.vehicle))
232
+ return withCustomText(
233
+ data,
234
+ vehicle ? `featuring a ${vehicle.label.toLowerCase()}, ${vehicle.description}` : "",
235
+ )
236
+ }
237
+ case "weapon": {
238
+ const weapon = getWeapon(asStr(data.weapon))
239
+ return withCustomText(
240
+ data,
241
+ weapon ? `with a ${weapon.label.toLowerCase()}, ${weapon.description}` : "",
242
+ )
243
+ }
244
+ case "furniture": {
245
+ const furniture = getFurniture(asStr(data.furniture))
246
+ return withCustomText(
247
+ data,
248
+ furniture ? `including a ${furniture.label.toLowerCase()}, ${furniture.description}` : "",
249
+ )
250
+ }
251
+ case "photo-genre":
252
+ return withCustomText(data, getPhotoGenrePromptHint(asStr(data.photoGenre)))
253
+ case "backdrop":
254
+ return withCustomText(data, getBackdropPromptHint(asStr(data.backdrop)))
255
+ case "held-prop":
256
+ return withCustomText(data, buildHeldPropHints(data.heldProp).join(", "))
257
+ case "photographer":
258
+ return withCustomText(data, buildPhotographerHints(data.photographer))
259
+ case "aesthetic":
260
+ return withCustomText(data, buildAestheticHints(data.aesthetic))
261
+ case "era":
262
+ return withCustomText(data, getEraPromptHint(asStr(data.era)))
263
+ case "temporal":
264
+ return withCustomText(data, buildTemporalHints(data).join(", "))
265
+ case "exposure-settings":
266
+ return withCustomText(data, buildExposureHints(data).join(", "))
267
+ case "render-quality":
268
+ return withCustomText(data, getRenderQualityPromptHint(asStr(data.renderQuality)))
269
+ case "composition-effects":
270
+ return withCustomText(data, getCompositionEffectPromptHint(asStr(data.compositionEffect)))
271
+ case "post-process-effects":
272
+ return withCustomText(data, buildPostProcessHints(data.postProcess).join(", "))
273
+
274
+ case "tone":
275
+ return asStr(data.tone).trim()
276
+ case "text-prompt":
277
+ return asStr(data.text).trim()
278
+ default:
279
+ return ""
280
+ }
281
+ }