@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/aesthetic.ts
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical catalog of Aesthetic / Microtrend choices.
|
|
3
|
+
*
|
|
4
|
+
* Single-pick parameter node — user picks ONE microtrend bundle that
|
|
5
|
+
* captures wardrobe + setting + grade + mood in a single descriptor.
|
|
6
|
+
* Microtrends are dense, model-recognised tokens (Y2K, dark academia,
|
|
7
|
+
* cottagecore, gorpcore...) that pull a coherent visual world along with
|
|
8
|
+
* the name. Each promptHint expands the slang into the specific wardrobe
|
|
9
|
+
* cues, environment cues and color treatment that define the look.
|
|
10
|
+
*
|
|
11
|
+
* Categories:
|
|
12
|
+
* - mainstream: high-recognition microtrends with a stable visual signature
|
|
13
|
+
* - niche: smaller -core / -kei / -punk movements
|
|
14
|
+
* - era: fashion eras / sensibilities (minimalism, maximalism, glam)
|
|
15
|
+
* - mood: cultural mood-bundles (effortless cool, main-character energy)
|
|
16
|
+
*
|
|
17
|
+
* Shared between the picker UI, the standalone Aesthetic parameter node,
|
|
18
|
+
* and the prompt-hint injection on both the frontend DAG executor and the
|
|
19
|
+
* backend orchestrator.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export type AestheticCategory = "mainstream" | "niche" | "era" | "mood"
|
|
23
|
+
|
|
24
|
+
export interface Aesthetic {
|
|
25
|
+
readonly id: string
|
|
26
|
+
readonly label: string
|
|
27
|
+
readonly category: AestheticCategory
|
|
28
|
+
readonly description: string
|
|
29
|
+
readonly promptHint: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const AESTHETICS: ReadonlyArray<Aesthetic> = [
|
|
33
|
+
// -------------------- Mainstream microtrends --------------------
|
|
34
|
+
{
|
|
35
|
+
id: "y2k",
|
|
36
|
+
label: "Y2K",
|
|
37
|
+
category: "mainstream",
|
|
38
|
+
description: "Late-90s / early-2000s pop tech",
|
|
39
|
+
promptHint:
|
|
40
|
+
"in the Y2K aesthetic — low-rise denim, baby tees, butterfly clips and chrome accessories, lit with sharp digicam flash and a faint chromatic gloss",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "cottagecore",
|
|
44
|
+
label: "Cottagecore",
|
|
45
|
+
category: "mainstream",
|
|
46
|
+
description: "Pastoral, hand-baked countryside",
|
|
47
|
+
promptHint:
|
|
48
|
+
"in the cottagecore aesthetic — linen dresses, gingham aprons and woven baskets in a sun-dappled cottage garden, with warm honeyed grading and gentle film grain",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "dark-academia",
|
|
52
|
+
label: "Dark Academia",
|
|
53
|
+
category: "mainstream",
|
|
54
|
+
description: "Old-world scholarship and tweed",
|
|
55
|
+
promptHint:
|
|
56
|
+
"in the dark academia aesthetic — tweed blazers, oxford shirts and stacks of leather-bound books in candlelit libraries, with a desaturated golden-shadow grade",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "light-academia",
|
|
60
|
+
label: "Light Academia",
|
|
61
|
+
category: "mainstream",
|
|
62
|
+
description: "Sunlit ivy-league scholarship",
|
|
63
|
+
promptHint:
|
|
64
|
+
"in the light academia aesthetic — cream cardigans, pleated skirts and open journals on sunlit terraces, with warm cream-and-honey color and softly washed highlights",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "techwear",
|
|
68
|
+
label: "Techwear / Gorpcore",
|
|
69
|
+
category: "mainstream",
|
|
70
|
+
description: "Tactical urban performance gear",
|
|
71
|
+
promptHint:
|
|
72
|
+
"in the techwear-gorpcore aesthetic — black tactical shells, MOLLE webbing, cargo pants and chunky trail runners in a rain-slick neon city, with cold cyan-magenta color",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "old-money",
|
|
76
|
+
label: "Old Money / Preppy",
|
|
77
|
+
category: "mainstream",
|
|
78
|
+
description: "Quiet-luxury heritage prep",
|
|
79
|
+
promptHint:
|
|
80
|
+
"in the old-money preppy aesthetic — cashmere sweaters, polo shirts, pearl strands and tailored linen on yacht decks and clay courts, with crisp, sun-bleached Mediterranean color",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "streetwear",
|
|
84
|
+
label: "Streetwear",
|
|
85
|
+
category: "mainstream",
|
|
86
|
+
description: "Sneaker-and-hoodie urban casual",
|
|
87
|
+
promptHint:
|
|
88
|
+
"in the streetwear aesthetic — graphic hoodies, oversized tees, premium sneakers and beanies on graffitied urban backdrops, with punchy contemporary color",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "coquette",
|
|
92
|
+
label: "Coquette",
|
|
93
|
+
category: "mainstream",
|
|
94
|
+
description: "Bows, lace and balletcore softness",
|
|
95
|
+
promptHint:
|
|
96
|
+
"in the coquette aesthetic — pink ribbons, lace trims, pearls and ballet flats in soft vanilla bedrooms, with rosy-pink grading and warm soft-focus glow",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: "fairycore",
|
|
100
|
+
label: "Fairycore",
|
|
101
|
+
category: "mainstream",
|
|
102
|
+
description: "Mossy, mushroom-strewn enchantment",
|
|
103
|
+
promptHint:
|
|
104
|
+
"in the fairycore aesthetic — flowing tulle, flower crowns and embroidered shawls in mossy enchanted forests, with iridescent dappled light and pastel-green color",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "goblincore",
|
|
108
|
+
label: "Goblincore",
|
|
109
|
+
category: "mainstream",
|
|
110
|
+
description: "Mossy, mud-loving wilderness joy",
|
|
111
|
+
promptHint:
|
|
112
|
+
"in the goblincore aesthetic — earth-toned knits, muddy boots and pockets full of mushrooms and beetles in damp forest undergrowth, with rich green-brown grading",
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: "normcore",
|
|
116
|
+
label: "Normcore",
|
|
117
|
+
category: "mainstream",
|
|
118
|
+
description: "Anti-fashion plain basics",
|
|
119
|
+
promptHint:
|
|
120
|
+
"in the normcore aesthetic — plain jeans, white sneakers, gray sweatshirts and bucket hats against banal everyday backdrops, with flat neutral color and even daylight",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: "balletcore",
|
|
124
|
+
label: "Balletcore",
|
|
125
|
+
category: "mainstream",
|
|
126
|
+
description: "Soft pink ballet-rehearsal grace",
|
|
127
|
+
promptHint:
|
|
128
|
+
"in the balletcore aesthetic — leg warmers, wrap cardigans, ribboned flats and tulle skirts in dust-floating studios, with soft pink-cream color and warm window light",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: "e-girl",
|
|
132
|
+
label: "E-Girl",
|
|
133
|
+
category: "mainstream",
|
|
134
|
+
description: "Webcore liner-and-blush internet",
|
|
135
|
+
promptHint:
|
|
136
|
+
"in the e-girl aesthetic — heart blush, winged liner, dyed split hair, plaid skirts and chain necklaces in bedroom LED light, with cool magenta-cyan color",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: "soft-girl",
|
|
140
|
+
label: "Soft Girl",
|
|
141
|
+
category: "mainstream",
|
|
142
|
+
description: "Pastel, sticker-bright sweetness",
|
|
143
|
+
promptHint:
|
|
144
|
+
"in the soft-girl aesthetic — pastel hoodies, butterfly clips, glossy lip and dainty heart stickers in cotton-candy-toned rooms, with warm peachy grading",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: "indie-sleaze",
|
|
148
|
+
label: "Indie Sleaze",
|
|
149
|
+
category: "mainstream",
|
|
150
|
+
description: "Tumblr-era flash-blown party",
|
|
151
|
+
promptHint:
|
|
152
|
+
"in the indie sleaze aesthetic — smudged eyeliner, leather jackets and skinny jeans at smoky house parties, lit by harsh on-camera flash with deep blacks and noisy grain",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: "baddie",
|
|
156
|
+
label: "Baddie",
|
|
157
|
+
category: "mainstream",
|
|
158
|
+
description: "Confident, styled, trend-savvy",
|
|
159
|
+
promptHint:
|
|
160
|
+
"in the baddie aesthetic — sharp-arched eyebrows, glossy lip, hoop earrings, bodycon dresses and bouncy curls in mirror-selfie poses, with high-contrast color and a polished glamorous finish",
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
// -------------------- Niche -core / -kei / -punk --------------------
|
|
164
|
+
{
|
|
165
|
+
id: "vacation-dadcore",
|
|
166
|
+
label: "Vacation Dadcore",
|
|
167
|
+
category: "niche",
|
|
168
|
+
description: "Hawaiian-shirt suburban-dad ease",
|
|
169
|
+
promptHint:
|
|
170
|
+
"in the vacation-dadcore aesthetic — Hawaiian shirts, cargo shorts, white socks and visor caps on cul-de-sac driveways, with bright tourist-snapshot color",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: "womancore",
|
|
174
|
+
label: "Womancore",
|
|
175
|
+
category: "niche",
|
|
176
|
+
description: "Confident, unfussy adult femininity",
|
|
177
|
+
promptHint:
|
|
178
|
+
"in the womancore aesthetic — tailored slacks, silk blouses and minimal jewelry in airy modern apartments, with soft natural light and a subdued elegant palette",
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: "angelcore",
|
|
182
|
+
label: "Angelcore",
|
|
183
|
+
category: "niche",
|
|
184
|
+
description: "Cherubic, lace-and-feather softness",
|
|
185
|
+
promptHint:
|
|
186
|
+
"in the angelcore aesthetic — flowing white lace, lace gloves, halo headbands and feathered wings against pale heaven-soft backdrops, with creamy pastel grading",
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: "hikecore",
|
|
190
|
+
label: "Hikecore",
|
|
191
|
+
category: "niche",
|
|
192
|
+
description: "Trail-day mountaineer chic",
|
|
193
|
+
promptHint:
|
|
194
|
+
"in the hikecore aesthetic — fleece pullovers, hiking boots, retro daypacks and topo-print baselayers on alpine trails, with crisp mountain color and high-altitude clarity",
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: "mote-kei",
|
|
198
|
+
label: "Mote-Kei",
|
|
199
|
+
category: "niche",
|
|
200
|
+
description: "Conservative-cute Japanese feminine",
|
|
201
|
+
promptHint:
|
|
202
|
+
"in the mote-kei aesthetic — pleated A-line skirts, soft knits, neat ribbons and pastel cardigans in sun-flooded Tokyo cafes, with delicate creamy color",
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: "kombuchapunk",
|
|
206
|
+
label: "Kombuchapunk",
|
|
207
|
+
category: "niche",
|
|
208
|
+
description: "Fermenting wellness counter-culture",
|
|
209
|
+
promptHint:
|
|
210
|
+
"in the kombuchapunk aesthetic — thrifted graphic tees, layered hemp, tarot decks and home-fermented jars in plant-cluttered shared kitchens, with warm earthy color",
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: "chinapunk",
|
|
214
|
+
label: "Chinapunk",
|
|
215
|
+
category: "niche",
|
|
216
|
+
description: "Y2K Hong Kong cyber glam",
|
|
217
|
+
promptHint:
|
|
218
|
+
"in the chinapunk aesthetic — qipao-cut crop tops, mesh sleeves, futuristic shades and chrome charms on neon Hong Kong streets, with electric magenta-cyan grading",
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: "karencore",
|
|
222
|
+
label: "Karencore",
|
|
223
|
+
category: "niche",
|
|
224
|
+
description: "Suburban-Karen blunt-bob power",
|
|
225
|
+
promptHint:
|
|
226
|
+
"in the karencore aesthetic — bobbed blonde hair, oversized sunglasses, cropped capris and white SUVs in sun-baked suburban parking lots, with bleached overexposed color",
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
id: "candycore",
|
|
230
|
+
label: "Candycore",
|
|
231
|
+
category: "niche",
|
|
232
|
+
description: "Sugar-rush rainbow saturation",
|
|
233
|
+
promptHint:
|
|
234
|
+
"in the candycore aesthetic — rainbow stripes, lollipop accessories, glittery eye shadow and platform sneakers in candy-bright sets, with maximum saturation",
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: "babycore",
|
|
238
|
+
label: "Babycore",
|
|
239
|
+
category: "niche",
|
|
240
|
+
description: "Pacifier-soft infantile pastel",
|
|
241
|
+
promptHint:
|
|
242
|
+
"in the babycore aesthetic — bonnets, ruffled rompers, oversized bows and stuffed animals against nursery-pastel backdrops, with milky soft-pink color",
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
id: "catcore",
|
|
246
|
+
label: "Catcore",
|
|
247
|
+
category: "niche",
|
|
248
|
+
description: "Cat-eared cozy domestic",
|
|
249
|
+
promptHint:
|
|
250
|
+
"in the catcore aesthetic — cat-ear hoodies, paw mittens, knitted cardigans and tabby-print scarves curled up by sun-warmed windows, with soft golden color",
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
id: "weirdcore",
|
|
254
|
+
label: "Weirdcore",
|
|
255
|
+
category: "niche",
|
|
256
|
+
description: "Surreal liminal-space unease",
|
|
257
|
+
promptHint:
|
|
258
|
+
"in the weirdcore aesthetic — surreal liminal-space anxiety blending glitch and nostalgia, low-fi muted color and JPEG artefacts, often empty hallways, fluorescent basements or backrooms-style pools rendered with quiet dread",
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: "dreamcore",
|
|
262
|
+
label: "Dreamcore",
|
|
263
|
+
category: "niche",
|
|
264
|
+
description: "Soft surreal dreamlike serenity",
|
|
265
|
+
promptHint:
|
|
266
|
+
"in the dreamcore aesthetic — dreamlike soft-focus surrealism with candy pastel palettes and uncanny serenity, often empty rooms, drifting clouds or abandoned playgrounds bathed in hazy diffuse light",
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
id: "traumacore",
|
|
270
|
+
label: "Traumacore",
|
|
271
|
+
category: "niche",
|
|
272
|
+
description: "Distorted nostalgic childhood collage",
|
|
273
|
+
promptHint:
|
|
274
|
+
"in the traumacore aesthetic — distorted nostalgic childhood imagery layered with melancholic captions and unsettling edits, Hello-Kitty stickers cut against scratched found-photo collage, washed-out pinks and faded sadness",
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
id: "alt-girl",
|
|
278
|
+
label: "Alt / Alt-Girl",
|
|
279
|
+
category: "niche",
|
|
280
|
+
description: "Grunge-meets-emo internet alt",
|
|
281
|
+
promptHint:
|
|
282
|
+
"in the alt-girl aesthetic — alternative subculture mix of grunge, scene, emo and e-girl with dyed split hair, layered chains, fishnets, oversized band tees and smudged dark eyeliner, lit with cool moody color",
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: "blokecore",
|
|
286
|
+
label: "Blokecore",
|
|
287
|
+
category: "niche",
|
|
288
|
+
description: "Football-jersey dadcore menswear",
|
|
289
|
+
promptHint:
|
|
290
|
+
"in the blokecore aesthetic — vintage football jerseys, Adidas Sambas, straight-leg denim, bucket hats and casual sportswear elevated to a fashion statement, on terraced streets with naturalistic everyday color",
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: "y2k-rave",
|
|
294
|
+
label: "Y2K Rave / Decora Club-Kid",
|
|
295
|
+
category: "niche",
|
|
296
|
+
description: "Neon Japanese decora rave kid",
|
|
297
|
+
promptHint:
|
|
298
|
+
"in the Y2K-rave decora club-kid aesthetic — fluorescent layered tops, plastic-charm bracelets, kawaii hair clips, leg warmers and platform sneakers under blacklight and neon strobes, with hyper-saturated electric color",
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
// -------------------- Fashion eras --------------------
|
|
302
|
+
{
|
|
303
|
+
id: "minimalism",
|
|
304
|
+
label: "Minimalism",
|
|
305
|
+
category: "era",
|
|
306
|
+
description: "Pared-down quiet-luxury restraint",
|
|
307
|
+
promptHint:
|
|
308
|
+
"in the minimalist aesthetic — clean monochrome tailoring, structured silhouettes and empty white-stone spaces, with restrained neutral color and even soft light",
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
id: "maximalism",
|
|
312
|
+
label: "Maximalism",
|
|
313
|
+
category: "era",
|
|
314
|
+
description: "More-is-more clashing pattern",
|
|
315
|
+
promptHint:
|
|
316
|
+
"in the maximalist aesthetic — clashing prints, layered jewelry, velvet, sequins and floral wallpaper in densely styled rooms, with rich saturated color",
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
id: "avant-garde",
|
|
320
|
+
label: "Avant-Garde",
|
|
321
|
+
category: "era",
|
|
322
|
+
description: "Conceptual sculptural fashion",
|
|
323
|
+
promptHint:
|
|
324
|
+
"in the avant-garde aesthetic — sculptural deconstructed silhouettes, exaggerated proportions and architectural draping in stark gallery-white spaces, with cold minimal color",
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
id: "old-hollywood-glam",
|
|
328
|
+
label: "Old Hollywood Glam",
|
|
329
|
+
category: "era",
|
|
330
|
+
description: "Silver-screen 1940s glamour",
|
|
331
|
+
promptHint:
|
|
332
|
+
"in the old-Hollywood-glam aesthetic — satin gowns, fur stoles, finger waves and red lipstick under sculpted studio key light, with silvery monochrome and high-contrast tonality",
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
// -------------------- Mood bundles --------------------
|
|
336
|
+
{
|
|
337
|
+
id: "main-character-energy",
|
|
338
|
+
label: "Main Character Energy",
|
|
339
|
+
category: "mood",
|
|
340
|
+
description: "Unbothered cinematic confidence",
|
|
341
|
+
promptHint:
|
|
342
|
+
"with main-character energy — unbothered confident posture, signature outfit and direct gaze, framed cinematically with shallow depth of field and warm filmic grading",
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
id: "soft-easygoing",
|
|
346
|
+
label: "Soft / Easygoing",
|
|
347
|
+
category: "mood",
|
|
348
|
+
description: "Gentle, low-effort warm calm",
|
|
349
|
+
promptHint:
|
|
350
|
+
"in a soft easygoing mood — relaxed knits, oversized layers and easy posture in softly lit rooms, with hazy natural light and creamy gentle color",
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
id: "casual-lived-in",
|
|
354
|
+
label: "Casual / Lived-In",
|
|
355
|
+
category: "mood",
|
|
356
|
+
description: "Worn-in, unstyled everyday",
|
|
357
|
+
promptHint:
|
|
358
|
+
"in a casual lived-in mood — worn jeans, faded tees and rumpled hair in everyday domestic settings, with flat naturalistic light and unpolished snapshot color",
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
id: "effortless-cool",
|
|
362
|
+
label: "Effortless Cool",
|
|
363
|
+
category: "mood",
|
|
364
|
+
description: "Unstudied off-duty French-girl ease",
|
|
365
|
+
promptHint:
|
|
366
|
+
"in an effortless-cool mood — slouchy knits, slip skirts, messy bangs and minimal makeup on cobbled European streets, with gentle desaturated film color",
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
id: "retro-90s",
|
|
370
|
+
label: "Retro 90s",
|
|
371
|
+
category: "mood",
|
|
372
|
+
description: "Throwback grunge-meets-mall 90s",
|
|
373
|
+
promptHint:
|
|
374
|
+
"in a retro 90s mood — flannel shirts, slip dresses, chokers and platform boots in mall food courts and bedroom posters, with slight VHS-grain and warm Kodachrome color",
|
|
375
|
+
},
|
|
376
|
+
] as const
|
|
377
|
+
|
|
378
|
+
const aestheticById = new Map<string, Aesthetic>(
|
|
379
|
+
AESTHETICS.map((a) => [a.id, a]),
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
export function getAesthetic(id: string | undefined | null): Aesthetic | undefined {
|
|
383
|
+
if (!id) return undefined
|
|
384
|
+
return aestheticById.get(id)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function getAestheticLabel(id: string | undefined | null, fallback?: string): string {
|
|
388
|
+
const a = getAesthetic(id)
|
|
389
|
+
if (a) return a.label
|
|
390
|
+
if (fallback !== undefined) return fallback
|
|
391
|
+
return (id ?? "").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function getAestheticPromptHint(id: string | undefined | null): string {
|
|
395
|
+
return getAesthetic(id)?.promptHint ?? ""
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Multi-pick variant: 1-2 aesthetic ids → blended hint. Single → entry's
|
|
400
|
+
* own promptHint. Two → "styled in a {A} + {B} aesthetic blend" with
|
|
401
|
+
* canonical entry labels (Y2K, dark academia, etc. stay as written).
|
|
402
|
+
*/
|
|
403
|
+
export function buildAestheticHints(value: unknown): string {
|
|
404
|
+
const ids: string[] = []
|
|
405
|
+
if (typeof value === "string" && value) ids.push(value)
|
|
406
|
+
else if (Array.isArray(value)) {
|
|
407
|
+
for (const v of value) {
|
|
408
|
+
if (typeof v === "string" && v && !ids.includes(v)) ids.push(v)
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (ids.length === 0) return ""
|
|
412
|
+
if (ids.length === 1) return getAestheticPromptHint(ids[0])
|
|
413
|
+
const labels = ids
|
|
414
|
+
.slice(0, 2)
|
|
415
|
+
.map((id) => getAesthetic(id)?.label ?? "")
|
|
416
|
+
.filter((s): s is string => Boolean(s))
|
|
417
|
+
if (labels.length < 2) return getAestheticPromptHint(ids[0])
|
|
418
|
+
return `styled in a ${labels[0]} + ${labels[1]} aesthetic blend`
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export const AESTHETIC_IDS: ReadonlyArray<string> = AESTHETICS.map((a) => a.id)
|
|
422
|
+
|
|
423
|
+
export const AESTHETIC_CATEGORY_LABELS: Readonly<Record<AestheticCategory, string>> = {
|
|
424
|
+
mainstream: "Mainstream",
|
|
425
|
+
niche: "Niche",
|
|
426
|
+
era: "Fashion Era",
|
|
427
|
+
mood: "Mood",
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export const AESTHETIC_CATEGORY_ORDER: ReadonlyArray<AestheticCategory> = [
|
|
431
|
+
"mainstream",
|
|
432
|
+
"niche",
|
|
433
|
+
"era",
|
|
434
|
+
"mood",
|
|
435
|
+
]
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `assembleImageInput` — the single source of truth for turning a node's
|
|
3
|
+
* image-generation inputs (a prompt + cinematic direction + connected
|
|
4
|
+
* references + the per-provider levers) into the flat `{ prompt,
|
|
5
|
+
* nativeNegativePrompt, referenceImageUrls }` the `generate-image` route
|
|
6
|
+
* expects.
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS EXISTS (WI-1a): the same three-step assembly —
|
|
9
|
+
* 1. compose the prompt text (fold cinematic id-hints + structured fields),
|
|
10
|
+
* 2. call `buildImagePrompt(...)` (the pure core; the per-provider reference
|
|
11
|
+
* gate lives INSIDE it),
|
|
12
|
+
* 3. (optionally) reject a truly-empty FINAL prompt,
|
|
13
|
+
* — was duplicated in THREE places kept in lockstep by hand: the frontend
|
|
14
|
+
* `execute-node.ts` `generate-image` branch, the backend
|
|
15
|
+
* `payload-builder.ts` `generate-image` case, and Studio's `assembly.ts`.
|
|
16
|
+
* This wrapper collapses them into one.
|
|
17
|
+
*
|
|
18
|
+
* THE NO-OP CONTRACT (load-bearing — the platform-caller parity relies on it):
|
|
19
|
+
* the two platform callers (`execute-node` / `payload-builder`) compose their
|
|
20
|
+
* prompt from the canvas graph themselves and pass NO cinematic `direction`
|
|
21
|
+
* ids and NO `structured` fields. In that case `composePromptText` MUST return
|
|
22
|
+
* the caller's `userPrompt` byte-for-byte unchanged, so the wrapper degenerates
|
|
23
|
+
* to exactly the `buildImagePrompt(...)` call those sites make today. Studio
|
|
24
|
+
* (and the MCP route) supply `direction` / `structured` and get the id-hint
|
|
25
|
+
* composition on top.
|
|
26
|
+
*
|
|
27
|
+
* THE EMPTY-CHECK FLAG (also load-bearing for parity): `execute-node` rejects a
|
|
28
|
+
* truly-empty assembled prompt (its "type one, mention a character, or connect
|
|
29
|
+
* a cinematography source" guard); `payload-builder` does NOT (it never threw
|
|
30
|
+
* there). So the post-assembly throw is OPT-IN via `throwOnEmpty` — defaulting
|
|
31
|
+
* to `false` preserves the backend's no-throw behavior. Callers that want the
|
|
32
|
+
* guard (frontend, Studio, route) pass `throwOnEmpty: true`.
|
|
33
|
+
*/
|
|
34
|
+
import {
|
|
35
|
+
buildImagePrompt,
|
|
36
|
+
type BuildImagePromptResult,
|
|
37
|
+
} from "./prompt-builder.js"
|
|
38
|
+
import { getFramingPromptHint } from "./framing.js"
|
|
39
|
+
import { getLightingPromptHint } from "./lighting.js"
|
|
40
|
+
import { getLensPromptHint } from "./lens.js"
|
|
41
|
+
import { getCameraFormatPromptHint } from "./camera-format.js"
|
|
42
|
+
import {
|
|
43
|
+
renderStructuredFields,
|
|
44
|
+
type StructuredPromptFields,
|
|
45
|
+
} from "./prompt-builder-structured-fields.js"
|
|
46
|
+
import type { CharacterDef, ConnectedReference, IdentityMeta } from "@nodaro/shared"
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Flat cinematic-direction ids the Studio framing UI (and the MCP route)
|
|
50
|
+
* expose — all optional. Promoted here from Studio's `assembly.ts` so the
|
|
51
|
+
* id → hint composition lives in one place. The platform callers pass none of
|
|
52
|
+
* these (they fold their hints from the graph into `userPrompt` themselves).
|
|
53
|
+
*/
|
|
54
|
+
export interface DirectionFields {
|
|
55
|
+
/** Shot Type — the FRAMINGS shot-size/coverage/composition/vantage dimensions. */
|
|
56
|
+
framingId?: string
|
|
57
|
+
/** Angle — the FRAMINGS angle dimension (separate pill, so it can coexist with Shot Type). */
|
|
58
|
+
framingAngleId?: string
|
|
59
|
+
lightingId?: string
|
|
60
|
+
lensId?: string
|
|
61
|
+
cameraFormatId?: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Input to `assembleImageInput`. A faithful SUPERSET of what the two platform
|
|
66
|
+
* callers pass to `buildImagePrompt` today (so they can route through this
|
|
67
|
+
* wrapper with byte-identical output), plus the id-based `direction` /
|
|
68
|
+
* `structured` composition levers that Studio + the MCP route use.
|
|
69
|
+
*/
|
|
70
|
+
export interface AssembleImageInput {
|
|
71
|
+
/** Pre-composed (caller's graph) or raw user prompt text. */
|
|
72
|
+
userPrompt: string
|
|
73
|
+
/** Image model id / provider key (the catalog enum value, e.g. "flux-2-max"). */
|
|
74
|
+
provider: string
|
|
75
|
+
/**
|
|
76
|
+
* Connected references with URLs ALREADY resolved by the caller. Become
|
|
77
|
+
* `referenceImageUrls` + identity directives inside `buildImagePrompt`
|
|
78
|
+
* (gated per provider there). Omit when the caller wires only raw URLs.
|
|
79
|
+
*/
|
|
80
|
+
connectedReferences?: ConnectedReference[]
|
|
81
|
+
/**
|
|
82
|
+
* Flat cinematic-direction ids → folded into the prompt as hints. Studio /
|
|
83
|
+
* MCP-route use; the platform callers pass none (so `composePromptText` is a
|
|
84
|
+
* no-op for them and the result is byte-identical to today).
|
|
85
|
+
*/
|
|
86
|
+
direction?: DirectionFields
|
|
87
|
+
/** Path-1 structured fields → composed fragment appended to the prompt. */
|
|
88
|
+
structured?: StructuredPromptFields
|
|
89
|
+
/**
|
|
90
|
+
* Reference image URLs from direct connections / manual uploads — ride
|
|
91
|
+
* `buildImagePrompt`'s reference-URL channel so they pass through the SAME
|
|
92
|
+
* per-provider reference-image gate + ordering as `connectedReferences`.
|
|
93
|
+
* (This is `buildImagePrompt`'s `referenceImageUrls` config field, named
|
|
94
|
+
* `extra…` here to reflect that bound entities already carry their URLs.)
|
|
95
|
+
*/
|
|
96
|
+
extraReferenceImageUrls?: string[]
|
|
97
|
+
/** Negative prompt text (routed to native vs. "Avoid:" by `buildImagePrompt`). */
|
|
98
|
+
negativePrompt?: string
|
|
99
|
+
/** Style text to append (e.g. "cinematic"). */
|
|
100
|
+
style?: string
|
|
101
|
+
/** User-defined reorder of the injected reference list (stable tile ids). */
|
|
102
|
+
referenceOrder?: readonly string[]
|
|
103
|
+
/** Per-identity (imageIndex+label) user overrides for fidelity / custom text. */
|
|
104
|
+
identityMeta?: readonly IdentityMeta[]
|
|
105
|
+
/** Character slugs whose canonical-fallback the user explicitly hid. */
|
|
106
|
+
suppressedCanonicalCharacterIds?: readonly string[]
|
|
107
|
+
/** Location slugs whose canonical-fallback the user explicitly hid. */
|
|
108
|
+
suppressedCanonicalLocationIds?: readonly string[]
|
|
109
|
+
/**
|
|
110
|
+
* Reference-prompt assembly format for the `{image:N:label}` path. Forwarded
|
|
111
|
+
* verbatim to `buildImagePrompt` (default legacy). "hybrid" = images-only
|
|
112
|
+
* reference-lock snippet + lettered inline scene.
|
|
113
|
+
*/
|
|
114
|
+
referenceFormat?: "legacy" | "hybrid"
|
|
115
|
+
/** Override the hybrid reference-lock snippet (forwarded to buildImagePrompt). */
|
|
116
|
+
referenceLockSnippet?: string
|
|
117
|
+
/** Character definitions selected for this node (legacy `buildImagePrompt` path). */
|
|
118
|
+
characterDefs?: CharacterDef[]
|
|
119
|
+
/** User-level prompt template overrides. */
|
|
120
|
+
userTemplates?: Record<string, string>
|
|
121
|
+
/** Flow-level prompt template overrides. */
|
|
122
|
+
flowTemplates?: Record<string, string>
|
|
123
|
+
/** Ancestor reference image URLs (fallback when no direct refs exist). */
|
|
124
|
+
ancestorRefs?: string[]
|
|
125
|
+
/** Map of `connectedReferences[i].id → sourceNodeId` for wired-raw tile ids. */
|
|
126
|
+
sourceNodeIdById?: ReadonlyMap<string, string>
|
|
127
|
+
/**
|
|
128
|
+
* LoRA inference path: strip `@`-mention tokens + skip the connected-reference
|
|
129
|
+
* machinery (the trigger word + LoRA carry identity).
|
|
130
|
+
*/
|
|
131
|
+
skipCharacterMentions?: boolean
|
|
132
|
+
/**
|
|
133
|
+
* Reject a truly-empty FINAL (post-assembly) prompt with `throw`. OFF by
|
|
134
|
+
* default to match the backend `payload-builder`, which never threw. The
|
|
135
|
+
* frontend / Studio / route pass `true` to keep their "type one, bind a
|
|
136
|
+
* character, or pick a cinematography direction" guard. Checked POST-assembly
|
|
137
|
+
* so a bound entity / `@`-mention / direction chip that filled an otherwise-
|
|
138
|
+
* empty prompt still runs.
|
|
139
|
+
*/
|
|
140
|
+
throwOnEmpty?: boolean
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Compose the cinematic-direction hints + structured-field fragment with the
|
|
145
|
+
* user's prompt. Each `get*PromptHint` returns "" on a miss, and
|
|
146
|
+
* `renderStructuredFields` returns "" when nothing is populated.
|
|
147
|
+
*
|
|
148
|
+
* EXACT NO-OP CONTRACT: when there are no cinematic/structured hint pieces (the
|
|
149
|
+
* platform-caller case — execute-node / payload-builder never pass `direction`/
|
|
150
|
+
* `structured`), the user's prompt is returned **verbatim, untrimmed**. This is
|
|
151
|
+
* load-bearing for parity: the old platform path passed the prompt straight to
|
|
152
|
+
* `buildImagePrompt`, which never trims, so trimming here would change the
|
|
153
|
+
* assembled prompt (and the recorded `jobs.input_data`) byte-for-byte. We only
|
|
154
|
+
* trim the user prompt when joining it WITH hints, so it reads cleanly
|
|
155
|
+
* ("prompt. hint", not "prompt . hint"). Never mutates inputs.
|
|
156
|
+
*/
|
|
157
|
+
function composePromptText(
|
|
158
|
+
userPrompt: string,
|
|
159
|
+
direction: DirectionFields | undefined,
|
|
160
|
+
structured: StructuredPromptFields | undefined,
|
|
161
|
+
): string {
|
|
162
|
+
const hints = [
|
|
163
|
+
getFramingPromptHint(direction?.framingId),
|
|
164
|
+
getFramingPromptHint(direction?.framingAngleId),
|
|
165
|
+
getLightingPromptHint(direction?.lightingId),
|
|
166
|
+
getLensPromptHint(direction?.lensId),
|
|
167
|
+
getCameraFormatPromptHint(direction?.cameraFormatId),
|
|
168
|
+
structured ? renderStructuredFields(structured) : "",
|
|
169
|
+
].filter((p) => p.length > 0)
|
|
170
|
+
// No hints → verbatim (exact no-op = platform parity). With hints → trim the
|
|
171
|
+
// user prompt so the ". " join is clean. The trailing filter drops a blank
|
|
172
|
+
// user prompt so the join never starts with ". " (parity-critical — don't
|
|
173
|
+
// remove it as "redundant": `hints` is pre-filtered but `userPrompt` is not).
|
|
174
|
+
if (hints.length === 0) return userPrompt
|
|
175
|
+
return [userPrompt.trim(), ...hints].filter((p) => p.length > 0).join(". ")
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Assemble a node's image-generation inputs into a `BuildImagePromptResult`
|
|
180
|
+
* (`{ prompt, nativeNegativePrompt, referenceImageUrls }`).
|
|
181
|
+
*
|
|
182
|
+
* Order: (1) compose the prompt text (no-op when no direction/structured),
|
|
183
|
+
* (2) `buildImagePrompt(...)` — exactly the call the three sites make today,
|
|
184
|
+
* (3) optional post-assembly empty-prompt throw (gated by `throwOnEmpty`).
|
|
185
|
+
*/
|
|
186
|
+
export function assembleImageInput(
|
|
187
|
+
input: AssembleImageInput,
|
|
188
|
+
): BuildImagePromptResult {
|
|
189
|
+
const prompt = composePromptText(input.userPrompt, input.direction, input.structured)
|
|
190
|
+
|
|
191
|
+
const result = buildImagePrompt({
|
|
192
|
+
prompt,
|
|
193
|
+
provider: input.provider,
|
|
194
|
+
...(input.connectedReferences !== undefined
|
|
195
|
+
? { connectedReferences: input.connectedReferences }
|
|
196
|
+
: {}),
|
|
197
|
+
// Manual uploads / direct refs ride the builder's reference-URL channel so
|
|
198
|
+
// the per-provider reference gate filters them alongside bound entities.
|
|
199
|
+
// Omit the field entirely when absent so the builder's default ([]) kicks
|
|
200
|
+
// in — byte-identical to a caller that didn't set it.
|
|
201
|
+
...(input.extraReferenceImageUrls !== undefined
|
|
202
|
+
? { referenceImageUrls: input.extraReferenceImageUrls }
|
|
203
|
+
: {}),
|
|
204
|
+
...(input.negativePrompt !== undefined ? { negativePrompt: input.negativePrompt } : {}),
|
|
205
|
+
...(input.style !== undefined ? { style: input.style } : {}),
|
|
206
|
+
...(input.referenceOrder !== undefined ? { referenceOrder: input.referenceOrder } : {}),
|
|
207
|
+
...(input.identityMeta !== undefined ? { identityMeta: input.identityMeta } : {}),
|
|
208
|
+
...(input.suppressedCanonicalCharacterIds !== undefined
|
|
209
|
+
? { suppressedCanonicalCharacterIds: input.suppressedCanonicalCharacterIds }
|
|
210
|
+
: {}),
|
|
211
|
+
...(input.suppressedCanonicalLocationIds !== undefined
|
|
212
|
+
? { suppressedCanonicalLocationIds: input.suppressedCanonicalLocationIds }
|
|
213
|
+
: {}),
|
|
214
|
+
...(input.referenceFormat !== undefined ? { referenceFormat: input.referenceFormat } : {}),
|
|
215
|
+
...(input.referenceLockSnippet !== undefined ? { referenceLockSnippet: input.referenceLockSnippet } : {}),
|
|
216
|
+
...(input.characterDefs !== undefined ? { characterDefs: input.characterDefs } : {}),
|
|
217
|
+
...(input.userTemplates !== undefined ? { userTemplates: input.userTemplates } : {}),
|
|
218
|
+
...(input.flowTemplates !== undefined ? { flowTemplates: input.flowTemplates } : {}),
|
|
219
|
+
...(input.ancestorRefs !== undefined ? { ancestorRefs: input.ancestorRefs } : {}),
|
|
220
|
+
...(input.sourceNodeIdById !== undefined ? { sourceNodeIdById: input.sourceNodeIdById } : {}),
|
|
221
|
+
...(input.skipCharacterMentions !== undefined
|
|
222
|
+
? { skipCharacterMentions: input.skipCharacterMentions }
|
|
223
|
+
: {}),
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
// Post-assembly empty-prompt check (opt-in): a bound entity / `@`-mention /
|
|
227
|
+
// direction chip could have filled the assembled prompt even if the user
|
|
228
|
+
// typed nothing — so only reject when the FINAL prompt is truly empty.
|
|
229
|
+
if (input.throwOnEmpty && !result.prompt.trim()) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
"No prompt — type one, bind a character, or pick a cinematography direction",
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return result
|
|
236
|
+
}
|