@nodaro/prompts 1.6.0 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Parameter-picker WIRING — the single source of truth for how each picker
3
+ * node type binds to its catalog: value field(s), defaults, entries, grouping,
4
+ * and per-field option lists for multi-dim pickers.
5
+ *
6
+ * Pure data, no React. Extracted from the app's parameter-picker-registry so
7
+ * THREE consumers share one definition and cannot drift:
8
+ * 1. The app's community fallback registry (chip pickers, no rich previews).
9
+ * 2. `@nodaroai/picker-ui`'s rich registry (attaches preview/Picker renderers).
10
+ * 3. Nodaro Cine's builder panels.
11
+ *
12
+ * Renderers (preview components, multi-dim Picker components) deliberately do
13
+ * NOT live here — presentation is the private package's concern; this file is
14
+ * the public vocabulary.
15
+ */
16
+ import {
17
+ ANIMALS,
18
+ ANIMAL_SUBCATEGORY_LABELS,
19
+ ANIMAL_SUBCATEGORY_ORDER,
20
+ VEHICLES,
21
+ VEHICLE_SUBCATEGORY_LABELS,
22
+ VEHICLE_SUBCATEGORY_ORDER,
23
+ WEAPONS,
24
+ WEAPON_SUBCATEGORY_LABELS,
25
+ WEAPON_SUBCATEGORY_ORDER,
26
+ FURNITURE,
27
+ FURNITURE_SUBCATEGORY_LABELS,
28
+ FURNITURE_SUBCATEGORY_ORDER,
29
+ type I18nCatalogId,
30
+ } from "@nodaro/shared"
31
+ import { SETTINGS, SETTING_CATEGORY_LABELS } from "./setting.js"
32
+ import { MATERIALS, MATERIAL_CATEGORY_LABELS, MATERIAL_CATEGORY_ORDER } from "./materials.js"
33
+ import { ATMOSPHERES } from "./atmosphere.js"
34
+ import { STYLES } from "./style.js"
35
+ import { MOODS, MOOD_CATEGORY_LABELS, MOOD_CATEGORY_ORDER } from "./mood.js"
36
+ import { POSES, POSE_CATEGORY_LABELS, POSE_CATEGORY_ORDER } from "./pose.js"
37
+ import { CAMERA_MOTIONS, CAMERA_MOTION_CATEGORY_LABELS, CAMERA_MOTION_CATEGORY_ORDER } from "./camera-motions.js"
38
+ import { LENSES } from "./lens.js"
39
+ import { CAMERA_FORMATS } from "./camera-format.js"
40
+ import { COLOR_LOOKS, COLOR_LOOK_CATEGORY_LABELS, COLOR_LOOK_CATEGORY_ORDER } from "./color-look.js"
41
+ import { PHOTOGRAPHERS, PHOTOGRAPHER_CATEGORY_LABELS, PHOTOGRAPHER_CATEGORY_ORDER } from "./photographer.js"
42
+ import { AESTHETICS, AESTHETIC_CATEGORY_LABELS, AESTHETIC_CATEGORY_ORDER } from "./aesthetic.js"
43
+ import { ERAS, ERA_CATEGORY_LABELS, ERA_CATEGORY_ORDER } from "./era.js"
44
+ import { PHOTO_GENRES, PHOTO_GENRE_CATEGORY_LABELS, PHOTO_GENRE_CATEGORY_ORDER } from "./photo-genre.js"
45
+ import { BACKDROPS, BACKDROP_CATEGORY_LABELS, BACKDROP_CATEGORY_ORDER } from "./backdrop.js"
46
+ import { HELD_PROPS, HELD_PROP_CATEGORY_LABELS, HELD_PROP_CATEGORY_ORDER } from "./held-prop.js"
47
+ import { RENDER_QUALITIES } from "./render-quality.js"
48
+ import { COMPOSITION_EFFECTS } from "./composition-effects.js"
49
+ import { POST_PROCESS_EFFECTS } from "./post-process-effects.js"
50
+ import { ACTION_FX, ACTION_FX_CATEGORY_LABELS, ACTION_FX_CATEGORY_ORDER } from "./action-fx.js"
51
+ import { LOOP_SUBJECTS, LOOP_SUBJECT_CATEGORY_LABELS, LOOP_SUBJECT_CATEGORY_ORDER } from "./loop-subject.js"
52
+ import { TRANSITIONS, TRANSITION_CATEGORY_LABELS, TRANSITION_CATEGORY_ORDER } from "./transitions.js"
53
+ import { CHARACTER_FX, CHARACTER_FX_CATEGORY_LABELS, CHARACTER_FX_CATEGORY_ORDER } from "./character-fx.js"
54
+ import { FRAMINGS, FRAMING_CATEGORY_ORDER, FRAMING_FIELD_BY_CATEGORY } from "./framing.js"
55
+ import { LIGHTINGS, LIGHTING_CATEGORY_ORDER, LIGHTING_FIELD_BY_CATEGORY } from "./lighting.js"
56
+ import { STYLINGS, STYLING_DIMENSION_ORDER, STYLING_FIELD_BY_DIMENSION } from "./styling.js"
57
+ import { PEOPLE, PERSON_DIMENSION_ORDER, PERSON_FIELD_BY_DIMENSION } from "./person.js"
58
+ import { TEMPORALS } from "./temporal.js"
59
+ import { EXPOSURE_SETTINGS } from "./exposure-settings.js"
60
+ import { MUSIC_GENRES, MUSIC_ERAS } from "./music-genre.js"
61
+ import { MUSIC_ENERGIES, MUSIC_EMOTIONS, MUSIC_VIBES } from "./music-mood.js"
62
+ import { INSTRUMENTS, PRODUCTION_STYLES, VOCAL_PRESENCE, SINGING_STYLES } from "./instrumentation.js"
63
+ import { VOICE_AGES, VOICE_GENDERS, VOICE_LANGUAGES, VOICE_ACCENTS, VOICE_TIMBRES } from "./voice-character.js"
64
+ import { VOICE_PACES, VOICE_EMOTIONS, VOICE_ARCHETYPES } from "./voice-delivery.js"
65
+
66
+ /** One selectable catalog entry as picker UIs consume it. */
67
+ export interface PickerWiringEntry {
68
+ readonly id: string
69
+ readonly label: string
70
+ readonly description: string
71
+ /** Optional group key for category headers. */
72
+ readonly group?: string
73
+ }
74
+
75
+ export interface SingleDimPickerWiring {
76
+ readonly kind: "single"
77
+ readonly nodeType: string
78
+ readonly label: string
79
+ readonly valueField: string
80
+ readonly defaultValue: string
81
+ readonly catalogId: I18nCatalogId
82
+ readonly entries: ReadonlyArray<PickerWiringEntry>
83
+ readonly groupOrder?: ReadonlyArray<string>
84
+ readonly groupLabels?: Readonly<Record<string, string>>
85
+ }
86
+
87
+ export interface MultiDimPickerWiring {
88
+ readonly kind: "multi"
89
+ readonly nodeType: string
90
+ readonly label: string
91
+ /** Data fields the picker reads/writes — used to slice node.data into a value object. */
92
+ readonly fields: ReadonlyArray<string>
93
+ readonly catalogId: I18nCatalogId
94
+ /** Flat id→label list — used to resolve ids into labels for summary chips. */
95
+ readonly catalogEntries: ReadonlyArray<{ readonly id: string; readonly label: string }>
96
+ /** Per-field option lists — lets a data-only consumer (community fallback,
97
+ * Cine simple mode) render one select per field without knowing the
98
+ * catalog's discriminator scheme. */
99
+ readonly fieldOptions: Readonly<Record<string, ReadonlyArray<{ readonly id: string; readonly label: string }>>>
100
+ }
101
+
102
+ export type PickerWiring = SingleDimPickerWiring | MultiDimPickerWiring
103
+
104
+ function mapCat<T extends { id: string; label: string; description: string }>(
105
+ arr: ReadonlyArray<T>,
106
+ groupKey?: keyof T,
107
+ ): ReadonlyArray<PickerWiringEntry> {
108
+ return arr.map((e) => ({
109
+ id: e.id,
110
+ label: e.label,
111
+ description: e.description,
112
+ group: groupKey ? (e[groupKey] as unknown as string) : undefined,
113
+ }))
114
+ }
115
+
116
+ function flatCat<T extends { id: string; label: string }>(
117
+ arr: ReadonlyArray<T>,
118
+ ): ReadonlyArray<{ id: string; label: string }> {
119
+ return arr.map((e) => ({ id: e.id, label: e.label }))
120
+ }
121
+
122
+ /** Group a discriminated catalog into per-field option lists via key→field map. */
123
+ function optionsByDiscriminator<T extends { id: string; label: string }>(
124
+ arr: ReadonlyArray<T>,
125
+ keyOf: (e: T) => string,
126
+ fieldByKey: Readonly<Record<string, string>>,
127
+ ): Record<string, Array<{ id: string; label: string }>> {
128
+ const out: Record<string, Array<{ id: string; label: string }>> = {}
129
+ for (const e of arr) {
130
+ const field = fieldByKey[keyOf(e)]
131
+ if (!field) continue
132
+ ;(out[field] ??= []).push({ id: e.id, label: e.label })
133
+ }
134
+ return out
135
+ }
136
+
137
+ export const SINGLE_PICKER_WIRING: ReadonlyArray<SingleDimPickerWiring> = [
138
+ // -------- "Look" family --------
139
+ { kind: "single", nodeType: "setting", label: "Setting", valueField: "setting", defaultValue: "forest", catalogId: "setting", entries: mapCat(SETTINGS, "category"), groupOrder: ["indoor", "urban", "nature", "fantastical"], groupLabels: SETTING_CATEGORY_LABELS },
140
+ { kind: "single", nodeType: "atmosphere", label: "Atmosphere", valueField: "atmosphere", defaultValue: "clear", catalogId: "atmosphere", entries: mapCat(ATMOSPHERES) },
141
+ { kind: "single", nodeType: "style", label: "Style", valueField: "style", defaultValue: "cinematic", catalogId: "style", entries: mapCat(STYLES) },
142
+ { kind: "single", nodeType: "color-look", label: "Color / Look", valueField: "colorLook", defaultValue: "warm", catalogId: "color-look", entries: mapCat(COLOR_LOOKS, "category"), groupOrder: COLOR_LOOK_CATEGORY_ORDER as ReadonlyArray<string>, groupLabels: COLOR_LOOK_CATEGORY_LABELS as Record<string, string> },
143
+ { kind: "single", nodeType: "mood", label: "Mood", valueField: "mood", defaultValue: "calm", catalogId: "mood", entries: mapCat(MOODS, "category"), groupOrder: MOOD_CATEGORY_ORDER as ReadonlyArray<string>, groupLabels: MOOD_CATEGORY_LABELS },
144
+ { kind: "single", nodeType: "photographer", label: "Photographer / Artist", valueField: "photographer", defaultValue: "tim-walker", catalogId: "photographer", entries: mapCat(PHOTOGRAPHERS, "category"), groupOrder: PHOTOGRAPHER_CATEGORY_ORDER as ReadonlyArray<string>, groupLabels: PHOTOGRAPHER_CATEGORY_LABELS },
145
+ { kind: "single", nodeType: "aesthetic", label: "Aesthetic / Microtrend", valueField: "aesthetic", defaultValue: "y2k", catalogId: "aesthetic", entries: mapCat(AESTHETICS, "category"), groupOrder: AESTHETIC_CATEGORY_ORDER as ReadonlyArray<string>, groupLabels: AESTHETIC_CATEGORY_LABELS },
146
+ { kind: "single", nodeType: "era", label: "Era / Period", valueField: "era", defaultValue: "1990s-mall", catalogId: "era", entries: mapCat(ERAS, "category"), groupOrder: ERA_CATEGORY_ORDER as ReadonlyArray<string>, groupLabels: ERA_CATEGORY_LABELS },
147
+ { kind: "single", nodeType: "photo-genre", label: "Photo Genre", valueField: "photoGenre", defaultValue: "fashion-editorial", catalogId: "photo-genre", entries: mapCat(PHOTO_GENRES, "category"), groupOrder: PHOTO_GENRE_CATEGORY_ORDER as ReadonlyArray<string>, groupLabels: PHOTO_GENRE_CATEGORY_LABELS },
148
+ { kind: "single", nodeType: "backdrop", label: "Backdrop", valueField: "backdrop", defaultValue: "white-seamless", catalogId: "backdrop", entries: mapCat(BACKDROPS, "category"), groupOrder: BACKDROP_CATEGORY_ORDER as ReadonlyArray<string>, groupLabels: BACKDROP_CATEGORY_LABELS },
149
+ { kind: "single", nodeType: "render-quality", label: "Render Quality", valueField: "renderQuality", defaultValue: "raytracing", catalogId: "render-quality", entries: mapCat(RENDER_QUALITIES) },
150
+ { kind: "single", nodeType: "composition-effects", label: "Composition Effect", valueField: "compositionEffect", defaultValue: "bursting-through-frame", catalogId: "composition-effects", entries: mapCat(COMPOSITION_EFFECTS) },
151
+ { kind: "single", nodeType: "action-fx", label: "Action FX", valueField: "actionFx", defaultValue: "earthquake-tremor", catalogId: "action-fx", entries: mapCat(ACTION_FX, "category"), groupOrder: ACTION_FX_CATEGORY_ORDER as ReadonlyArray<string>, groupLabels: ACTION_FX_CATEGORY_LABELS as Record<string, string> },
152
+ { kind: "single", nodeType: "loop-subject", label: "Loop Subject", valueField: "loopSubject", defaultValue: "tunnel", catalogId: "loop-subject", entries: mapCat(LOOP_SUBJECTS, "category"), groupOrder: LOOP_SUBJECT_CATEGORY_ORDER as ReadonlyArray<string>, groupLabels: LOOP_SUBJECT_CATEGORY_LABELS as Record<string, string> },
153
+ { kind: "single", nodeType: "post-process-effects", label: "Post-Process Effect", valueField: "postProcess", defaultValue: "vignette-soft", catalogId: "post-process-effects", entries: mapCat(POST_PROCESS_EFFECTS) },
154
+
155
+ // -------- "Camera" family --------
156
+ { kind: "single", nodeType: "camera-motion", label: "Camera Motion", valueField: "cameraMotion", defaultValue: "static", catalogId: "camera-motions", entries: mapCat(CAMERA_MOTIONS, "category"), groupOrder: CAMERA_MOTION_CATEGORY_ORDER as ReadonlyArray<string>, groupLabels: CAMERA_MOTION_CATEGORY_LABELS },
157
+ { kind: "single", nodeType: "lens", label: "Lens", valueField: "lens", defaultValue: "normal-50mm", catalogId: "lens", entries: mapCat(LENSES) },
158
+ { kind: "single", nodeType: "camera-format", label: "Camera / Film", valueField: "cameraFormat", defaultValue: "35mm-film", catalogId: "camera-format", entries: mapCat(CAMERA_FORMATS) },
159
+ { kind: "single", nodeType: "transition", label: "Transition", valueField: "transition", defaultValue: "auto", catalogId: "transitions", entries: mapCat(TRANSITIONS, "category"), groupOrder: TRANSITION_CATEGORY_ORDER as ReadonlyArray<string>, groupLabels: TRANSITION_CATEGORY_LABELS },
160
+ { kind: "single", nodeType: "character-fx", label: "Character FX", valueField: "characterFx", defaultValue: "auto", catalogId: "character-fx", entries: mapCat(CHARACTER_FX, "category"), groupOrder: CHARACTER_FX_CATEGORY_ORDER as ReadonlyArray<string>, groupLabels: CHARACTER_FX_CATEGORY_LABELS },
161
+
162
+ // -------- "Subject / Object" family --------
163
+ { kind: "single", nodeType: "pose", label: "Pose", valueField: "pose", defaultValue: "standing-upright", catalogId: "pose", entries: mapCat(POSES, "category"), groupOrder: POSE_CATEGORY_ORDER as ReadonlyArray<string>, groupLabels: POSE_CATEGORY_LABELS },
164
+ { kind: "single", nodeType: "material", label: "Material", valueField: "material", defaultValue: "silk", catalogId: "materials", entries: mapCat(MATERIALS, "category"), groupOrder: MATERIAL_CATEGORY_ORDER as ReadonlyArray<string>, groupLabels: MATERIAL_CATEGORY_LABELS },
165
+ { kind: "single", nodeType: "animal", label: "Animal", valueField: "animal", defaultValue: "dog-golden-retriever", catalogId: "animals", entries: mapCat(ANIMALS, "subcategory"), groupOrder: ANIMAL_SUBCATEGORY_ORDER as ReadonlyArray<string>, groupLabels: ANIMAL_SUBCATEGORY_LABELS },
166
+ { kind: "single", nodeType: "vehicle", label: "Vehicle", valueField: "vehicle", defaultValue: "sedan", catalogId: "vehicles", entries: mapCat(VEHICLES, "subcategory"), groupOrder: VEHICLE_SUBCATEGORY_ORDER as ReadonlyArray<string>, groupLabels: VEHICLE_SUBCATEGORY_LABELS },
167
+ { kind: "single", nodeType: "weapon", label: "Weapon", valueField: "weapon", defaultValue: "katana", catalogId: "weapons", entries: mapCat(WEAPONS, "subcategory"), groupOrder: WEAPON_SUBCATEGORY_ORDER as ReadonlyArray<string>, groupLabels: WEAPON_SUBCATEGORY_LABELS },
168
+ { kind: "single", nodeType: "furniture", label: "Furniture", valueField: "furniture", defaultValue: "sofa", catalogId: "furniture", entries: mapCat(FURNITURE, "subcategory"), groupOrder: FURNITURE_SUBCATEGORY_ORDER as ReadonlyArray<string>, groupLabels: FURNITURE_SUBCATEGORY_LABELS },
169
+ { kind: "single", nodeType: "held-prop", label: "Held Prop", valueField: "heldProp", defaultValue: "smartphone", catalogId: "held-prop", entries: mapCat(HELD_PROPS, "category"), groupOrder: HELD_PROP_CATEGORY_ORDER as ReadonlyArray<string>, groupLabels: HELD_PROP_CATEGORY_LABELS },
170
+ ]
171
+
172
+ // Derived from the canonical dimension/category order + field map (same source
173
+ // the picker components and the describe-to-picker analyzer use). Deriving
174
+ // (not hand-listing) means these can never drift from the node-data shape.
175
+ const STYLING_FIELDS = STYLING_DIMENSION_ORDER.map((d) => STYLING_FIELD_BY_DIMENSION[d])
176
+ const PERSON_FIELDS = PERSON_DIMENSION_ORDER.map((d) => PERSON_FIELD_BY_DIMENSION[d])
177
+ const LIGHTING_FIELDS = LIGHTING_CATEGORY_ORDER.map((c) => LIGHTING_FIELD_BY_CATEGORY[c])
178
+
179
+ // Literal category→field maps for the two catalogs whose discriminators don't
180
+ // ship a shared FIELD_BY map (kept tiny + local; a wrong key = the field
181
+ // silently missing from fieldOptions, which the wiring guard test catches).
182
+ const TEMPORAL_FIELD_BY_CATEGORY: Readonly<Record<string, string>> = {
183
+ speed: "temporalSpeed",
184
+ freeze: "temporalFreeze",
185
+ direction: "temporalDirection",
186
+ shutter: "temporalShutter",
187
+ }
188
+ const EXPOSURE_FIELD_BY_CATEGORY: Readonly<Record<string, string>> = {
189
+ aperture: "aperture",
190
+ "shutter-speed": "shutterSpeed",
191
+ iso: "isoValue",
192
+ }
193
+
194
+ export const MULTI_PICKER_WIRING: ReadonlyArray<MultiDimPickerWiring> = [
195
+ {
196
+ kind: "multi",
197
+ nodeType: "framing",
198
+ label: "Framing",
199
+ fields: ["shotSize", "angle", "coverage", "composition", "vantage"],
200
+ catalogId: "framing",
201
+ catalogEntries: flatCat(FRAMINGS),
202
+ fieldOptions: optionsByDiscriminator(FRAMINGS, (e) => e.category, FRAMING_FIELD_BY_CATEGORY as Readonly<Record<string, string>>),
203
+ },
204
+ {
205
+ kind: "multi",
206
+ nodeType: "lighting",
207
+ label: "Lighting",
208
+ fields: LIGHTING_FIELDS,
209
+ catalogId: "lighting",
210
+ catalogEntries: flatCat(LIGHTINGS),
211
+ fieldOptions: optionsByDiscriminator(LIGHTINGS, (e) => e.category, LIGHTING_FIELD_BY_CATEGORY as Readonly<Record<string, string>>),
212
+ },
213
+ {
214
+ kind: "multi",
215
+ nodeType: "person",
216
+ label: "Person",
217
+ fields: PERSON_FIELDS,
218
+ catalogId: "person",
219
+ catalogEntries: flatCat(PEOPLE),
220
+ fieldOptions: optionsByDiscriminator(PEOPLE, (e) => e.dimension, PERSON_FIELD_BY_DIMENSION as Readonly<Record<string, string>>),
221
+ },
222
+ {
223
+ kind: "multi",
224
+ nodeType: "styling",
225
+ label: "Styling",
226
+ fields: STYLING_FIELDS,
227
+ catalogId: "styling",
228
+ catalogEntries: flatCat(STYLINGS),
229
+ fieldOptions: optionsByDiscriminator(STYLINGS, (e) => e.dimension, STYLING_FIELD_BY_DIMENSION as Readonly<Record<string, string>>),
230
+ },
231
+ {
232
+ kind: "multi",
233
+ nodeType: "temporal",
234
+ label: "Temporal",
235
+ fields: ["temporalSpeed", "temporalFreeze", "temporalDirection", "temporalShutter"],
236
+ catalogId: "temporal",
237
+ catalogEntries: flatCat(TEMPORALS),
238
+ fieldOptions: optionsByDiscriminator(TEMPORALS, (e) => e.category, TEMPORAL_FIELD_BY_CATEGORY),
239
+ },
240
+ {
241
+ kind: "multi",
242
+ nodeType: "exposure-settings",
243
+ label: "Exposure Settings",
244
+ fields: ["aperture", "shutterSpeed", "isoValue"],
245
+ catalogId: "exposure-settings",
246
+ catalogEntries: flatCat(EXPOSURE_SETTINGS),
247
+ fieldOptions: optionsByDiscriminator(EXPOSURE_SETTINGS, (e) => e.category, EXPOSURE_FIELD_BY_CATEGORY),
248
+ },
249
+ // -------- "Sound" family --------
250
+ // Music Genre catalog is hierarchical: flatten genres + every subgenre +
251
+ // eras so summary chips can resolve any selected id back to a human label.
252
+ {
253
+ kind: "multi",
254
+ nodeType: "music-genre",
255
+ label: "Music Genre",
256
+ fields: ["genre", "subgenre", "era"],
257
+ catalogId: "music-genre",
258
+ catalogEntries: [
259
+ ...flatCat(MUSIC_GENRES),
260
+ ...MUSIC_GENRES.flatMap((g) => g.subgenres.map((s) => ({ id: s.id, label: s.label }))),
261
+ ...flatCat(MUSIC_ERAS),
262
+ ],
263
+ fieldOptions: {
264
+ genre: flatCat(MUSIC_GENRES),
265
+ subgenre: MUSIC_GENRES.flatMap((g) => g.subgenres.map((s) => ({ id: s.id, label: s.label }))),
266
+ era: flatCat(MUSIC_ERAS),
267
+ },
268
+ },
269
+ {
270
+ kind: "multi",
271
+ nodeType: "music-mood",
272
+ label: "Music Mood",
273
+ fields: ["energy", "emotion", "vibe"],
274
+ catalogId: "music-mood",
275
+ catalogEntries: [...flatCat(MUSIC_ENERGIES), ...flatCat(MUSIC_EMOTIONS), ...flatCat(MUSIC_VIBES)],
276
+ fieldOptions: {
277
+ energy: flatCat(MUSIC_ENERGIES),
278
+ emotion: flatCat(MUSIC_EMOTIONS),
279
+ vibe: flatCat(MUSIC_VIBES),
280
+ },
281
+ },
282
+ {
283
+ kind: "multi",
284
+ nodeType: "instrumentation",
285
+ label: "Instrumentation",
286
+ fields: ["instruments", "production", "vocalPresence", "singingStyle"],
287
+ catalogId: "instrumentation",
288
+ catalogEntries: [...flatCat(INSTRUMENTS), ...flatCat(PRODUCTION_STYLES), ...flatCat(VOCAL_PRESENCE), ...flatCat(SINGING_STYLES)],
289
+ fieldOptions: {
290
+ instruments: flatCat(INSTRUMENTS),
291
+ production: flatCat(PRODUCTION_STYLES),
292
+ vocalPresence: flatCat(VOCAL_PRESENCE),
293
+ singingStyle: flatCat(SINGING_STYLES),
294
+ },
295
+ },
296
+ {
297
+ kind: "multi",
298
+ nodeType: "voice-character",
299
+ label: "Voice Character",
300
+ fields: ["age", "gender", "language", "accent", "timbre"],
301
+ catalogId: "voice-character",
302
+ catalogEntries: [...flatCat(VOICE_AGES), ...flatCat(VOICE_GENDERS), ...flatCat(VOICE_LANGUAGES), ...flatCat(VOICE_ACCENTS), ...flatCat(VOICE_TIMBRES)],
303
+ fieldOptions: {
304
+ age: flatCat(VOICE_AGES),
305
+ gender: flatCat(VOICE_GENDERS),
306
+ language: flatCat(VOICE_LANGUAGES),
307
+ accent: flatCat(VOICE_ACCENTS),
308
+ timbre: flatCat(VOICE_TIMBRES),
309
+ },
310
+ },
311
+ {
312
+ kind: "multi",
313
+ nodeType: "voice-delivery",
314
+ label: "Voice Delivery",
315
+ fields: ["pace", "emotion", "archetype"],
316
+ catalogId: "voice-delivery",
317
+ catalogEntries: [...flatCat(VOICE_PACES), ...flatCat(VOICE_EMOTIONS), ...flatCat(VOICE_ARCHETYPES)],
318
+ fieldOptions: {
319
+ pace: flatCat(VOICE_PACES),
320
+ emotion: flatCat(VOICE_EMOTIONS),
321
+ archetype: flatCat(VOICE_ARCHETYPES),
322
+ },
323
+ },
324
+ ]
325
+
326
+ export const ALL_PICKER_WIRING: ReadonlyArray<PickerWiring> = [
327
+ ...SINGLE_PICKER_WIRING,
328
+ ...MULTI_PICKER_WIRING,
329
+ ]
330
+
331
+ const WIRING_MAP = new Map<string, PickerWiring>(ALL_PICKER_WIRING.map((w) => [w.nodeType, w]))
332
+
333
+ export function getPickerWiring(nodeType: string | undefined | null): PickerWiring | undefined {
334
+ if (!nodeType) return undefined
335
+ return WIRING_MAP.get(nodeType)
336
+ }
@@ -166,6 +166,7 @@ export const PROVIDER_CAPABILITIES: Record<string, Record<string, string>> = {
166
166
  "gpt-image": "Creative concepts, illustration, variable quality tiers",
167
167
  "gpt-image-2": "Latest GPT Image — sharp text, photorealism, 1K/2K/4K resolution",
168
168
  "grok": "General purpose, good text understanding",
169
+ "grok-2": "Grok Imagine 2 — expressive, high-contrast, stylized output",
169
170
  "imagen4": "Google's latest, strong photorealism and text rendering",
170
171
  "imagen4-fast": "Faster Imagen 4 variant",
171
172
  "imagen4-ultra": "Highest quality Imagen 4",
@@ -242,7 +243,7 @@ export const PROVIDER_CAPABILITIES: Record<string, Record<string, string>> = {
242
243
  "seedance-2": "Seedance 2.0 — multimodal refs (9 images / 3 videos / 3 audio), native multi-track audio, multi-shot storytelling, 4-15s",
243
244
  "seedance-2-fast": "Seedance 2.0 Fast — same multimodal + audio capabilities, cheaper and quicker",
244
245
  "seedance-2-mini": "Seedance 2.0 Mini — same multimodal + audio capabilities, budget tier, 480p/720p, 4-15s",
245
- "seedance-2-5": "Seedance 2.5 — up to 30s in ONE shot (no stitching), wider multimodal refs (30 images / 10 videos / 10 audio), native audio, 480p/720p",
246
+ "seedance-2-5": "Seedance 2.5 — up to 30s in ONE shot (no stitching), wider multimodal refs (30 images / 10 videos / 10 audio), native audio, 480p/720p/1080p",
246
247
  "minimax-h3": "MiniMax Hailuo 3 — premium multimodal refs (9 images / 3 videos / 3 audio), always-on audio, 2K or 768P, 4-15s per-second pricing",
247
248
  "wan": "Versatile, good for animations and transformations",
248
249
  "wan-turbo": "Faster Wan generation",
@@ -270,7 +271,7 @@ export const PROVIDER_CAPABILITIES: Record<string, Record<string, string>> = {
270
271
  "seedance-2": "Seedance 2.0 — start/end frame + multimodal refs, native audio, 4-15s",
271
272
  "seedance-2-fast": "Seedance 2.0 Fast — same capabilities, cheaper and quicker",
272
273
  "seedance-2-mini": "Seedance 2.0 Mini — same capabilities, budget tier, 480p/720p",
273
- "seedance-2-5": "Seedance 2.5 — start/end frame + wide multimodal refs, native audio, up to 30s, 480p/720p",
274
+ "seedance-2-5": "Seedance 2.5 — start/end frame + wide multimodal refs, native audio, up to 30s, 480p/720p/1080p",
274
275
  "minimax-h3": "MiniMax Hailuo 3 — first/last frame + multimodal refs, always-on audio, 2K or 768P, 4-15s",
275
276
  "hailuo-2.3-pro": "Premium Hailuo animation",
276
277
  "hailuo-2.3": "Standard Hailuo animation",