@nodaro/prompts 1.5.0 → 1.7.0
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/dist/index.cjs +670 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +609 -2
- package/dist/index.d.ts +609 -2
- package/dist/index.js +660 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/assemble-suno-input.test.ts +20 -0
- package/src/__tests__/doctrine-roster-completeness.test.ts +59 -0
- package/src/__tests__/location-convergence-image.test.ts +6 -2
- package/src/__tests__/picker-analyzer-registry.test.ts +11 -1
- package/src/__tests__/provider-prompt-doctrine.test.ts +5 -3
- package/src/__tests__/reference-rules.test.ts +240 -0
- package/src/__tests__/video-reference-features.test.ts +1 -0
- package/src/assemble-suno-input.ts +8 -0
- package/src/factory-snippets/catalog.ts +19 -1
- package/src/index.ts +2 -0
- package/src/picker-analyzer-registry.ts +190 -0
- package/src/picker-wiring.ts +336 -0
- package/src/prompt-wizard-categories.ts +4 -0
- package/src/provider-prompt-doctrine.ts +279 -6
- package/src/reference-rules.ts +226 -0
|
@@ -10,6 +10,36 @@ import { STYLINGS, STYLING_DIMENSION_ORDER, STYLING_DIMENSION_LABELS, STYLING_FI
|
|
|
10
10
|
import { FRAMINGS, FRAMING_CATEGORY_ORDER, FRAMING_CATEGORY_LABELS, FRAMING_FIELD_BY_CATEGORY, getFramingCategoryLimit } from "./framing.js"
|
|
11
11
|
import { LENSES } from "./lens.js"
|
|
12
12
|
import { CAMERA_FORMATS } from "./camera-format.js"
|
|
13
|
+
import { ANIMALS, VEHICLES, WEAPONS, FURNITURE } from "@nodaro/shared"
|
|
14
|
+
import { SETTINGS } from "./setting.js"
|
|
15
|
+
import { ATMOSPHERES } from "./atmosphere.js"
|
|
16
|
+
import { STYLES } from "./style.js"
|
|
17
|
+
import { MOODS } from "./mood.js"
|
|
18
|
+
import { COLOR_LOOKS } from "./color-look.js"
|
|
19
|
+
import { PHOTOGRAPHERS } from "./photographer.js"
|
|
20
|
+
import { AESTHETICS } from "./aesthetic.js"
|
|
21
|
+
import { ERAS } from "./era.js"
|
|
22
|
+
import { PHOTO_GENRES } from "./photo-genre.js"
|
|
23
|
+
import { BACKDROPS } from "./backdrop.js"
|
|
24
|
+
import { RENDER_QUALITIES } from "./render-quality.js"
|
|
25
|
+
import { COMPOSITION_EFFECTS } from "./composition-effects.js"
|
|
26
|
+
import { POST_PROCESS_EFFECTS } from "./post-process-effects.js"
|
|
27
|
+
import { ACTION_FX } from "./action-fx.js"
|
|
28
|
+
import { LOOP_SUBJECTS } from "./loop-subject.js"
|
|
29
|
+
import { TRANSITIONS } from "./transitions.js"
|
|
30
|
+
import { CHARACTER_FX } from "./character-fx.js"
|
|
31
|
+
import { POSES } from "./pose.js"
|
|
32
|
+
import { MATERIALS } from "./materials.js"
|
|
33
|
+
import { HELD_PROPS } from "./held-prop.js"
|
|
34
|
+
import { CAMERA_MOTIONS } from "./camera-motions.js"
|
|
35
|
+
import { LIGHTINGS, LIGHTING_CATEGORY_ORDER, LIGHTING_CATEGORY_LABELS, LIGHTING_FIELD_BY_CATEGORY } from "./lighting.js"
|
|
36
|
+
import { TEMPORALS } from "./temporal.js"
|
|
37
|
+
import { EXPOSURE_SETTINGS } from "./exposure-settings.js"
|
|
38
|
+
import { MUSIC_GENRES, MUSIC_ERAS } from "./music-genre.js"
|
|
39
|
+
import { MUSIC_ENERGIES, MUSIC_EMOTIONS, MUSIC_VIBES } from "./music-mood.js"
|
|
40
|
+
import { INSTRUMENTS, PRODUCTION_STYLES, VOCAL_PRESENCE, SINGING_STYLES } from "./instrumentation.js"
|
|
41
|
+
import { VOICE_AGES, VOICE_GENDERS, VOICE_LANGUAGES, VOICE_ACCENTS, VOICE_TIMBRES } from "./voice-character.js"
|
|
42
|
+
import { VOICE_PACES, VOICE_EMOTIONS, VOICE_ARCHETYPES } from "./voice-delivery.js"
|
|
13
43
|
|
|
14
44
|
// ─── Descriptor model ────────────────────────────────────────────────────────
|
|
15
45
|
|
|
@@ -57,6 +87,16 @@ export type PickerAnalyzerDescriptor =
|
|
|
57
87
|
|
|
58
88
|
const PERSON_EXCLUDED = new Set<string>(["age-custom"])
|
|
59
89
|
|
|
90
|
+
/** Tag catalog entries with an explicit dimension — for discriminated pickers
|
|
91
|
+
* whose fields live in SEPARATE catalogs (music/voice) rather than one
|
|
92
|
+
* discriminated catalog. */
|
|
93
|
+
function tagDim<T extends { id: string; label: string; description: string }>(
|
|
94
|
+
arr: ReadonlyArray<T>,
|
|
95
|
+
dimension: string,
|
|
96
|
+
): ReadonlyArray<AnalyzerEntry> {
|
|
97
|
+
return arr.map((e) => ({ id: e.id, label: e.label, description: e.description, dimension }))
|
|
98
|
+
}
|
|
99
|
+
|
|
60
100
|
const personCleanup: ApplyCleanup = (patch, mode) => {
|
|
61
101
|
if (mode === "override") {
|
|
62
102
|
patch.customAge = undefined
|
|
@@ -113,10 +153,160 @@ export const PICKER_ANALYZER_REGISTRY = {
|
|
|
113
153
|
label: "Camera / Film Stock",
|
|
114
154
|
entries: CAMERA_FORMATS as ReadonlyArray<AnalyzerEntry>,
|
|
115
155
|
},
|
|
156
|
+
|
|
157
|
+
// ─── Text-to-picker expansion (Cine AI Fill): every remaining catalog ─────
|
|
158
|
+
// Flat single-value pickers — field names match the picker wiring's
|
|
159
|
+
// valueField (and the node-data shape the published-app input card writes).
|
|
160
|
+
setting: { kind: "flat", toolName: "emit_setting", field: "setting", label: "Setting", entries: SETTINGS as ReadonlyArray<AnalyzerEntry> },
|
|
161
|
+
atmosphere: { kind: "flat", toolName: "emit_atmosphere", field: "atmosphere", label: "Atmosphere", entries: ATMOSPHERES as ReadonlyArray<AnalyzerEntry> },
|
|
162
|
+
style: { kind: "flat", toolName: "emit_style", field: "style", label: "Style", entries: STYLES as ReadonlyArray<AnalyzerEntry> },
|
|
163
|
+
mood: { kind: "flat", toolName: "emit_mood", field: "mood", label: "Mood", entries: MOODS as ReadonlyArray<AnalyzerEntry> },
|
|
164
|
+
"color-look": { kind: "flat", toolName: "emit_color_look", field: "colorLook", label: "Color / Look", entries: COLOR_LOOKS as ReadonlyArray<AnalyzerEntry> },
|
|
165
|
+
photographer: { kind: "flat", toolName: "emit_photographer", field: "photographer", label: "Photographer / Artist", entries: PHOTOGRAPHERS as ReadonlyArray<AnalyzerEntry> },
|
|
166
|
+
aesthetic: { kind: "flat", toolName: "emit_aesthetic", field: "aesthetic", label: "Aesthetic / Microtrend", entries: AESTHETICS as ReadonlyArray<AnalyzerEntry> },
|
|
167
|
+
era: { kind: "flat", toolName: "emit_era", field: "era", label: "Era / Period", entries: ERAS as ReadonlyArray<AnalyzerEntry> },
|
|
168
|
+
"photo-genre": { kind: "flat", toolName: "emit_photo_genre", field: "photoGenre", label: "Photo Genre", entries: PHOTO_GENRES as ReadonlyArray<AnalyzerEntry> },
|
|
169
|
+
backdrop: { kind: "flat", toolName: "emit_backdrop", field: "backdrop", label: "Backdrop", entries: BACKDROPS as ReadonlyArray<AnalyzerEntry> },
|
|
170
|
+
"render-quality": { kind: "flat", toolName: "emit_render_quality", field: "renderQuality", label: "Render Quality", entries: RENDER_QUALITIES as ReadonlyArray<AnalyzerEntry> },
|
|
171
|
+
"composition-effects": { kind: "flat", toolName: "emit_composition_effects", field: "compositionEffect", label: "Composition Effect", entries: COMPOSITION_EFFECTS as ReadonlyArray<AnalyzerEntry> },
|
|
172
|
+
"post-process-effects": { kind: "flat", toolName: "emit_post_process_effects", field: "postProcess", label: "Post-Process Effect", entries: POST_PROCESS_EFFECTS as ReadonlyArray<AnalyzerEntry> },
|
|
173
|
+
"action-fx": { kind: "flat", toolName: "emit_action_fx", field: "actionFx", label: "Action FX", entries: ACTION_FX as ReadonlyArray<AnalyzerEntry> },
|
|
174
|
+
"loop-subject": { kind: "flat", toolName: "emit_loop_subject", field: "loopSubject", label: "Loop Subject", entries: LOOP_SUBJECTS as ReadonlyArray<AnalyzerEntry> },
|
|
175
|
+
transition: { kind: "flat", toolName: "emit_transition", field: "transition", label: "Transition", entries: TRANSITIONS as ReadonlyArray<AnalyzerEntry> },
|
|
176
|
+
"character-fx": { kind: "flat", toolName: "emit_character_fx", field: "characterFx", label: "Character FX", entries: CHARACTER_FX as ReadonlyArray<AnalyzerEntry> },
|
|
177
|
+
pose: { kind: "flat", toolName: "emit_pose", field: "pose", label: "Pose", entries: POSES as ReadonlyArray<AnalyzerEntry> },
|
|
178
|
+
material: { kind: "flat", toolName: "emit_material", field: "material", label: "Material", entries: MATERIALS as ReadonlyArray<AnalyzerEntry> },
|
|
179
|
+
"held-prop": { kind: "flat", toolName: "emit_held_prop", field: "heldProp", label: "Held Prop", entries: HELD_PROPS as ReadonlyArray<AnalyzerEntry> },
|
|
180
|
+
"camera-motion": { kind: "flat", toolName: "emit_camera_motion", field: "cameraMotion", label: "Camera Motion", entries: CAMERA_MOTIONS as ReadonlyArray<AnalyzerEntry> },
|
|
181
|
+
animal: { kind: "flat", toolName: "emit_animal", field: "animal", label: "Animal", entries: ANIMALS as ReadonlyArray<AnalyzerEntry> },
|
|
182
|
+
vehicle: { kind: "flat", toolName: "emit_vehicle", field: "vehicle", label: "Vehicle", entries: VEHICLES as ReadonlyArray<AnalyzerEntry> },
|
|
183
|
+
weapon: { kind: "flat", toolName: "emit_weapon", field: "weapon", label: "Weapon", entries: WEAPONS as ReadonlyArray<AnalyzerEntry> },
|
|
184
|
+
furniture: { kind: "flat", toolName: "emit_furniture", field: "furniture", label: "Furniture", entries: FURNITURE as ReadonlyArray<AnalyzerEntry> },
|
|
185
|
+
|
|
186
|
+
// Discriminated multi-dim pickers. lighting/temporal/exposure discriminate
|
|
187
|
+
// on the single catalog's `category`; the sound/voice pickers span several
|
|
188
|
+
// per-field catalogs, so their entries are synthesized with an explicit
|
|
189
|
+
// `dimension` tag (tagDim below) — same wire shape either way.
|
|
190
|
+
lighting: {
|
|
191
|
+
kind: "discriminated",
|
|
192
|
+
toolName: "emit_lighting",
|
|
193
|
+
discriminator: "category",
|
|
194
|
+
order: LIGHTING_CATEGORY_ORDER as ReadonlyArray<string>,
|
|
195
|
+
fieldByKey: LIGHTING_FIELD_BY_CATEGORY as Readonly<Record<string, string>>,
|
|
196
|
+
labels: LIGHTING_CATEGORY_LABELS as Readonly<Record<string, string>>,
|
|
197
|
+
entries: LIGHTINGS as ReadonlyArray<AnalyzerEntry>,
|
|
198
|
+
limitFn: () => 1,
|
|
199
|
+
},
|
|
200
|
+
temporal: {
|
|
201
|
+
kind: "discriminated",
|
|
202
|
+
toolName: "emit_temporal",
|
|
203
|
+
discriminator: "category",
|
|
204
|
+
order: ["speed", "freeze", "direction", "shutter"],
|
|
205
|
+
fieldByKey: { speed: "temporalSpeed", freeze: "temporalFreeze", direction: "temporalDirection", shutter: "temporalShutter" },
|
|
206
|
+
labels: { speed: "Playback Speed", freeze: "Freeze", direction: "Direction", shutter: "Shutter" },
|
|
207
|
+
entries: TEMPORALS as ReadonlyArray<AnalyzerEntry>,
|
|
208
|
+
limitFn: () => 1,
|
|
209
|
+
},
|
|
210
|
+
"exposure-settings": {
|
|
211
|
+
kind: "discriminated",
|
|
212
|
+
toolName: "emit_exposure_settings",
|
|
213
|
+
discriminator: "category",
|
|
214
|
+
order: ["aperture", "shutter-speed", "iso"],
|
|
215
|
+
fieldByKey: { aperture: "aperture", "shutter-speed": "shutterSpeed", iso: "isoValue" },
|
|
216
|
+
labels: { aperture: "Aperture", "shutter-speed": "Shutter Speed", iso: "ISO" },
|
|
217
|
+
entries: EXPOSURE_SETTINGS as ReadonlyArray<AnalyzerEntry>,
|
|
218
|
+
limitFn: () => 1,
|
|
219
|
+
},
|
|
220
|
+
"music-genre": {
|
|
221
|
+
kind: "discriminated",
|
|
222
|
+
toolName: "emit_music_genre",
|
|
223
|
+
discriminator: "dimension",
|
|
224
|
+
order: ["genre", "subgenre", "era"],
|
|
225
|
+
fieldByKey: { genre: "genre", subgenre: "subgenre", era: "era" },
|
|
226
|
+
labels: { genre: "Genre", subgenre: "Subgenre", era: "Era" },
|
|
227
|
+
entries: [
|
|
228
|
+
...tagDim(MUSIC_GENRES, "genre"),
|
|
229
|
+
// Subgenres carry promptHint but no description — the hint doubles as
|
|
230
|
+
// the legend text (it describes the sound well enough for matching).
|
|
231
|
+
...MUSIC_GENRES.flatMap((g) =>
|
|
232
|
+
g.subgenres.map((s) => ({ id: s.id, label: s.label, description: s.promptHint, dimension: "subgenre" })),
|
|
233
|
+
),
|
|
234
|
+
...tagDim(MUSIC_ERAS, "era"),
|
|
235
|
+
],
|
|
236
|
+
limitFn: (k) => (k === "genre" ? 2 : 1),
|
|
237
|
+
},
|
|
238
|
+
"music-mood": {
|
|
239
|
+
kind: "discriminated",
|
|
240
|
+
toolName: "emit_music_mood",
|
|
241
|
+
discriminator: "dimension",
|
|
242
|
+
order: ["energy", "emotion", "vibe"],
|
|
243
|
+
fieldByKey: { energy: "energy", emotion: "emotion", vibe: "vibe" },
|
|
244
|
+
labels: { energy: "Energy", emotion: "Emotion", vibe: "Vibe" },
|
|
245
|
+
entries: [...tagDim(MUSIC_ENERGIES, "energy"), ...tagDim(MUSIC_EMOTIONS, "emotion"), ...tagDim(MUSIC_VIBES, "vibe")],
|
|
246
|
+
limitFn: (k) => (k === "energy" ? 1 : 2),
|
|
247
|
+
},
|
|
248
|
+
instrumentation: {
|
|
249
|
+
kind: "discriminated",
|
|
250
|
+
toolName: "emit_instrumentation",
|
|
251
|
+
discriminator: "dimension",
|
|
252
|
+
order: ["instruments", "production", "vocalPresence", "singingStyle"],
|
|
253
|
+
fieldByKey: { instruments: "instruments", production: "production", vocalPresence: "vocalPresence", singingStyle: "singingStyle" },
|
|
254
|
+
labels: { instruments: "Instruments", production: "Production Style", vocalPresence: "Vocal Presence", singingStyle: "Singing Style" },
|
|
255
|
+
entries: [
|
|
256
|
+
...tagDim(INSTRUMENTS, "instruments"),
|
|
257
|
+
...tagDim(PRODUCTION_STYLES, "production"),
|
|
258
|
+
...tagDim(VOCAL_PRESENCE, "vocalPresence"),
|
|
259
|
+
...tagDim(SINGING_STYLES, "singingStyle"),
|
|
260
|
+
],
|
|
261
|
+
limitFn: (k) => (k === "instruments" ? 3 : k === "production" ? 1 : 2),
|
|
262
|
+
},
|
|
263
|
+
"voice-character": {
|
|
264
|
+
kind: "discriminated",
|
|
265
|
+
toolName: "emit_voice_character",
|
|
266
|
+
discriminator: "dimension",
|
|
267
|
+
order: ["age", "gender", "language", "accent", "timbre"],
|
|
268
|
+
fieldByKey: { age: "age", gender: "gender", language: "language", accent: "accent", timbre: "timbre" },
|
|
269
|
+
labels: { age: "Age", gender: "Gender", language: "Language", accent: "Accent", timbre: "Timbre" },
|
|
270
|
+
entries: [
|
|
271
|
+
...tagDim(VOICE_AGES, "age"),
|
|
272
|
+
...tagDim(VOICE_GENDERS, "gender"),
|
|
273
|
+
...tagDim(VOICE_LANGUAGES, "language"),
|
|
274
|
+
...tagDim(VOICE_ACCENTS, "accent"),
|
|
275
|
+
...tagDim(VOICE_TIMBRES, "timbre"),
|
|
276
|
+
],
|
|
277
|
+
limitFn: (k) => (k === "language" ? 2 : 1),
|
|
278
|
+
},
|
|
279
|
+
"voice-delivery": {
|
|
280
|
+
kind: "discriminated",
|
|
281
|
+
toolName: "emit_voice_delivery",
|
|
282
|
+
discriminator: "dimension",
|
|
283
|
+
order: ["pace", "emotion", "archetype"],
|
|
284
|
+
fieldByKey: { pace: "pace", emotion: "emotion", archetype: "archetype" },
|
|
285
|
+
labels: { pace: "Pace", emotion: "Emotion", archetype: "Archetype" },
|
|
286
|
+
entries: [...tagDim(VOICE_PACES, "pace"), ...tagDim(VOICE_EMOTIONS, "emotion"), ...tagDim(VOICE_ARCHETYPES, "archetype")],
|
|
287
|
+
limitFn: () => 1,
|
|
288
|
+
},
|
|
116
289
|
} satisfies Record<string, PickerAnalyzerDescriptor>
|
|
117
290
|
|
|
118
291
|
export type PickerType = keyof typeof PICKER_ANALYZER_REGISTRY
|
|
119
292
|
export const PICKER_TYPES = Object.keys(PICKER_ANALYZER_REGISTRY) as PickerType[]
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Family grouping for BATCHED analysis. A single call across all 38 catalogs
|
|
296
|
+
* carries a ~211k-char legend (~53k tokens — measured 2026-08-09, the
|
|
297
|
+
* measure-first probe from the text-to-picker spec), which is slow, costly,
|
|
298
|
+
* and dilutes per-section accuracy. The text-to-picker route fans out one
|
|
299
|
+
* structured call per family (6-15k tokens each) and merges. Mirrors the
|
|
300
|
+
* build-brief's §5 UI grouping so Cine can reuse the same partition.
|
|
301
|
+
*/
|
|
302
|
+
export const PICKER_ANALYZER_FAMILIES: Readonly<Record<string, ReadonlyArray<PickerType>>> = {
|
|
303
|
+
scene: ["setting", "atmosphere", "backdrop", "era", "temporal"],
|
|
304
|
+
look: ["style", "color-look", "mood", "aesthetic", "photographer", "photo-genre", "render-quality", "composition-effects", "post-process-effects"],
|
|
305
|
+
camera: ["framing", "camera-motion", "lens", "camera-format", "lighting", "exposure-settings"],
|
|
306
|
+
character: ["person", "styling", "pose", "character-fx"],
|
|
307
|
+
elements: ["animal", "vehicle", "weapon", "furniture", "held-prop", "material", "action-fx", "loop-subject", "transition"],
|
|
308
|
+
audio: ["music-genre", "music-mood", "instrumentation", "voice-character", "voice-delivery"],
|
|
309
|
+
}
|
|
120
310
|
export const ANALYZABLE_PICKER_TYPES: ReadonlySet<string> = new Set(PICKER_TYPES)
|
|
121
311
|
export function isAnalyzablePicker(t: string): t is PickerType {
|
|
122
312
|
return ANALYZABLE_PICKER_TYPES.has(t)
|
|
@@ -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
|
+
}
|
|
@@ -242,6 +242,8 @@ export const PROVIDER_CAPABILITIES: Record<string, Record<string, string>> = {
|
|
|
242
242
|
"seedance-2": "Seedance 2.0 — multimodal refs (9 images / 3 videos / 3 audio), native multi-track audio, multi-shot storytelling, 4-15s",
|
|
243
243
|
"seedance-2-fast": "Seedance 2.0 Fast — same multimodal + audio capabilities, cheaper and quicker",
|
|
244
244
|
"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
|
+
"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",
|
|
245
247
|
"wan": "Versatile, good for animations and transformations",
|
|
246
248
|
"wan-turbo": "Faster Wan generation",
|
|
247
249
|
"hailuo-standard": "Standard quality, cost-effective",
|
|
@@ -268,6 +270,8 @@ export const PROVIDER_CAPABILITIES: Record<string, Record<string, string>> = {
|
|
|
268
270
|
"seedance-2": "Seedance 2.0 — start/end frame + multimodal refs, native audio, 4-15s",
|
|
269
271
|
"seedance-2-fast": "Seedance 2.0 Fast — same capabilities, cheaper and quicker",
|
|
270
272
|
"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
|
+
"minimax-h3": "MiniMax Hailuo 3 — first/last frame + multimodal refs, always-on audio, 2K or 768P, 4-15s",
|
|
271
275
|
"hailuo-2.3-pro": "Premium Hailuo animation",
|
|
272
276
|
"hailuo-2.3": "Standard Hailuo animation",
|
|
273
277
|
"hailuo-standard": "Cost-effective animation",
|