@nodaro/prompts 1.26.1 → 1.28.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 +3127 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1776 -10
- package/dist/index.d.ts +1776 -10
- package/dist/index.js +3094 -93
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/fixtures/parameter-hint-golden.json +33 -1
- package/src/__tests__/hint-join.test.ts +64 -1
- package/src/__tests__/motion-hint.test.ts +20 -0
- package/src/__tests__/parameter-registry-sync.test.ts +1 -0
- package/src/__tests__/person-mixed-heritage.test.ts +43 -0
- package/src/__tests__/picker-art-images.test.ts +191 -0
- package/src/__tests__/resolve-prompt.test.ts +13 -1
- package/src/__tests__/transition-options.test.ts +223 -0
- package/src/__tests__/transition-styles.test.ts +202 -0
- package/src/__tests__/transitions-instant.test.ts +9 -5
- package/src/__tests__/transitions-scope.test.ts +593 -8
- package/src/__tests__/video-own-motion.test.ts +26 -0
- package/src/catalog-sections.ts +24 -0
- package/src/hint-join.ts +33 -1
- package/src/index.ts +4 -0
- package/src/parameter-prompt-hint.ts +20 -3
- package/src/person.ts +24 -8
- package/src/picker-art/character-art-files.generated.ts +1118 -0
- package/src/picker-art/images.ts +83 -0
- package/src/picker-art/index.ts +13 -0
- package/src/picker-art/look-preview-sets.ts +489 -0
- package/src/picker-art/paths.ts +53 -0
- package/src/picker-art/sound-art-files.generated.ts +319 -0
- package/src/picker-art/sound-art-map.ts +722 -0
- package/src/picker-art/sound-art-types.ts +19 -0
- package/src/picker-catalogs.ts +125 -24
- package/src/prompt-builder.ts +2 -1
- package/src/prompt-hint-join.ts +8 -2
- package/src/prompt-style-section.ts +3 -5
- package/src/resolve-prompt.ts +22 -4
- package/src/styling.ts +18 -0
- package/src/transitions.ts +320 -51
- package/src/video-own-motion.ts +29 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** The dimension fields of each music / voice catalog that carries art. */
|
|
2
|
+
export interface SoundArtFields {
|
|
3
|
+
"music-genre": "genre" | "subgenre" | "era"
|
|
4
|
+
"music-mood": "energy" | "emotion" | "vibe"
|
|
5
|
+
instrumentation: "instruments" | "production" | "vocalPresence" | "singingStyle"
|
|
6
|
+
"voice-character": "age" | "gender" | "language" | "accent" | "timbre"
|
|
7
|
+
"voice-delivery": "pace" | "emotion" | "archetype"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type SoundArtCatalogId = keyof SoundArtFields
|
|
11
|
+
|
|
12
|
+
/** `emoji/<slug>` or `flags/<code>` — a key of SOUND_ART_FILES. */
|
|
13
|
+
export type SoundArtKey = `emoji/${string}` | `flags/${string}`
|
|
14
|
+
|
|
15
|
+
export type SoundArtMap = {
|
|
16
|
+
readonly [C in SoundArtCatalogId]: {
|
|
17
|
+
readonly [F in SoundArtFields[C]]: Readonly<Record<string, SoundArtKey>>
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/picker-catalogs.ts
CHANGED
|
@@ -81,7 +81,8 @@ import { ANIMALS, ANIMAL_SUBCATEGORY_LABELS, ANIMAL_SUBCATEGORY_ORDER, getAnimal
|
|
|
81
81
|
import { VEHICLES, VEHICLE_SUBCATEGORY_LABELS, VEHICLE_SUBCATEGORY_ORDER } from "@nodaro/shared"
|
|
82
82
|
import { WEAPONS, WEAPON_SUBCATEGORY_LABELS, WEAPON_SUBCATEGORY_ORDER } from "@nodaro/shared"
|
|
83
83
|
import { FURNITURE, FURNITURE_SUBCATEGORY_LABELS, FURNITURE_SUBCATEGORY_ORDER } from "@nodaro/shared"
|
|
84
|
-
import type { ProjectedCatalog, ProjectedCatalogOption, ProjectedCatalogDimension } from "@nodaro/shared"
|
|
84
|
+
import type { ProjectedCatalog, ProjectedCatalogOption, ProjectedCatalogDimension, ProjectedCatalogSection } from "@nodaro/shared"
|
|
85
|
+
import { pickerOptionImageUrl, pickerSectionImageUrl, type PickerImageOptions } from "./picker-art/images.js"
|
|
85
86
|
import { HELD_PROPS, HELD_PROP_CATEGORY_LABELS, HELD_PROP_CATEGORY_ORDER } from "./held-prop.js"
|
|
86
87
|
import { FRAMINGS, FRAMING_FIELD_BY_CATEGORY, FRAMING_CATEGORY_LABELS } from "./framing.js"
|
|
87
88
|
import { LIGHTINGS, LIGHTING_FIELD_BY_CATEGORY, LIGHTING_CATEGORY_ORDER, LIGHTING_CATEGORY_LABELS } from "./lighting.js"
|
|
@@ -89,6 +90,7 @@ import { TEMPORALS, TEMPORAL_FIELD_BY_CATEGORY, TEMPORAL_CATEGORY_LABELS } from
|
|
|
89
90
|
import { EXPOSURE_SETTINGS, EXPOSURE_FIELD_BY_CATEGORY, EXPOSURE_CATEGORY_LABELS } from "./exposure-settings.js"
|
|
90
91
|
import { PEOPLE, PERSON_FIELD_BY_DIMENSION, PERSON_DIMENSION_ORDER, PERSON_DIMENSION_LABELS } from "./person.js"
|
|
91
92
|
import { STYLINGS, STYLING_FIELD_BY_DIMENSION, STYLING_DIMENSION_ORDER, STYLING_DIMENSION_LABELS } from "./styling.js"
|
|
93
|
+
import { catalogTopics } from "./catalog-sections.js"
|
|
92
94
|
import { MUSIC_GENRES, MUSIC_ERAS } from "./music-genre.js"
|
|
93
95
|
import { MUSIC_ENERGIES, MUSIC_EMOTIONS, MUSIC_VIBES } from "./music-mood.js"
|
|
94
96
|
import { INSTRUMENTS, PRODUCTION_STYLES, VOCAL_PRESENCE, SINGING_STYLES } from "./instrumentation.js"
|
|
@@ -129,6 +131,17 @@ export interface PickerOption {
|
|
|
129
131
|
* duration" — the safe reading, since the composer then behaves as before.
|
|
130
132
|
*/
|
|
131
133
|
readonly instant?: true
|
|
134
|
+
/**
|
|
135
|
+
* PER-OPTION PARAMETERS — extra node-data fields that apply only while THIS
|
|
136
|
+
* option is picked, each with its own rows (Transitions: a wipe's
|
|
137
|
+
* `wipeDirection`; see `Transition.options`). Same shape as the catalog's
|
|
138
|
+
* `dimensions`, but scoped to the row that declares them: a consumer shows
|
|
139
|
+
* the control while the row is picked and stores the chosen row id under
|
|
140
|
+
* `field` beside the pick. The `auto` row leads each list and means "send
|
|
141
|
+
* nothing"; a row's `promptHint` is the phrase it writes into the option's
|
|
142
|
+
* description, not a clause of its own. Absent on every other option.
|
|
143
|
+
*/
|
|
144
|
+
readonly params?: readonly PickerDimension[]
|
|
132
145
|
/** Only present if the source catalog entry already carries a data icon/emoji/thumbnail field. */
|
|
133
146
|
readonly icon?: string
|
|
134
147
|
}
|
|
@@ -184,6 +197,18 @@ interface BaseCatalogEntry {
|
|
|
184
197
|
readonly adultOnly?: true
|
|
185
198
|
/** Transitions: see `Transition.instant`. Propagated into the flattened option as `instant: true`. */
|
|
186
199
|
readonly instant?: boolean
|
|
200
|
+
/** Transitions: see `Transition.options`. Propagated as the option's `params`. */
|
|
201
|
+
readonly options?: ReadonlyArray<{
|
|
202
|
+
readonly field: string
|
|
203
|
+
readonly label: string
|
|
204
|
+
readonly choices: ReadonlyArray<{
|
|
205
|
+
readonly id: string
|
|
206
|
+
readonly label: string
|
|
207
|
+
readonly description: string
|
|
208
|
+
readonly phrase: string
|
|
209
|
+
readonly term: string
|
|
210
|
+
}>
|
|
211
|
+
}>
|
|
187
212
|
}
|
|
188
213
|
|
|
189
214
|
/**
|
|
@@ -213,6 +238,19 @@ function toOptions<T extends BaseCatalogEntry>(
|
|
|
213
238
|
if (categoryField) opt.category = e[categoryField] as unknown as string
|
|
214
239
|
if (e.adultOnly) opt.adultOnly = true
|
|
215
240
|
if (e.instant) opt.instant = true
|
|
241
|
+
if (e.options?.length) {
|
|
242
|
+
opt.params = e.options.map((o) => ({
|
|
243
|
+
field: o.field,
|
|
244
|
+
label: o.label,
|
|
245
|
+
options: o.choices.map((c) => ({
|
|
246
|
+
id: c.id,
|
|
247
|
+
label: c.label,
|
|
248
|
+
description: c.description,
|
|
249
|
+
promptHint: c.phrase,
|
|
250
|
+
term: c.term,
|
|
251
|
+
})),
|
|
252
|
+
}))
|
|
253
|
+
}
|
|
216
254
|
return opt as PickerOption
|
|
217
255
|
})
|
|
218
256
|
}
|
|
@@ -908,23 +946,41 @@ export interface PickerCatalogSummary {
|
|
|
908
946
|
readonly fields?: readonly string[]
|
|
909
947
|
/** single: options.length; multi: sum of every dimension's options. */
|
|
910
948
|
readonly optionCount: number
|
|
949
|
+
/** How many of those options carry an `imageUrl` (0 without picture options). */
|
|
950
|
+
readonly imageCount: number
|
|
911
951
|
}
|
|
912
952
|
|
|
913
|
-
/**
|
|
914
|
-
|
|
915
|
-
return
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
953
|
+
/** Every (field, option) pair a catalog's detail call returns, whichever kind. */
|
|
954
|
+
function everyOption(c: PickerCatalog): ReadonlyArray<{ readonly field: string | undefined; readonly option: PickerOption }> {
|
|
955
|
+
return [
|
|
956
|
+
...(c.options ?? []).map((option) => ({ field: c.valueField, option })),
|
|
957
|
+
...(c.dimensions ?? []).flatMap((d) => d.options.map((option) => ({ field: d.field, option }))),
|
|
958
|
+
]
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* Lightweight directory of every picker catalog — no option payloads. With
|
|
963
|
+
* `images`, `imageCount` says how many options the detail call pictures.
|
|
964
|
+
*/
|
|
965
|
+
export function summarizePickerCatalogs(opts: { readonly images?: PickerImageOptions } = {}): readonly PickerCatalogSummary[] {
|
|
966
|
+
return getRegisteredPickerCatalogs().map((c) => {
|
|
967
|
+
const all = everyOption(c)
|
|
968
|
+
const images = opts.images
|
|
969
|
+
return {
|
|
970
|
+
nodeType: c.nodeType,
|
|
971
|
+
label: c.label,
|
|
972
|
+
catalogId: c.catalogId,
|
|
973
|
+
kind: c.kind,
|
|
974
|
+
valueField: c.valueField,
|
|
975
|
+
fields: c.fields,
|
|
976
|
+
// Every option the detail call will return, whichever kind — a single-dim
|
|
977
|
+
// catalog with secondary dimensions carries both.
|
|
978
|
+
optionCount: all.length,
|
|
979
|
+
imageCount: images
|
|
980
|
+
? all.filter(({ field, option }) => pickerOptionImageUrl(c, field, option.id, images) !== undefined).length
|
|
981
|
+
: 0,
|
|
982
|
+
}
|
|
983
|
+
})
|
|
928
984
|
}
|
|
929
985
|
|
|
930
986
|
export type PickerCatalogDetail = "compact" | "full"
|
|
@@ -936,6 +992,12 @@ export interface ProjectPickerCatalogOptions {
|
|
|
936
992
|
readonly category?: string
|
|
937
993
|
/** multi-dim: keep only this dimension field. */
|
|
938
994
|
readonly field?: string
|
|
995
|
+
/**
|
|
996
|
+
* Attach each option's absolute `imageUrl` (and the Person / Styling topic
|
|
997
|
+
* pictures). Left out, no option carries one — the caller (the API) knows
|
|
998
|
+
* which host this installation is reached at, the package does not.
|
|
999
|
+
*/
|
|
1000
|
+
readonly images?: PickerImageOptions
|
|
939
1001
|
}
|
|
940
1002
|
|
|
941
1003
|
// The projection shape now lives in `@nodaro/shared` (Apache) as the tag-free
|
|
@@ -946,10 +1008,29 @@ export type ProjectedPickerOption = ProjectedCatalogOption
|
|
|
946
1008
|
export type ProjectedPickerDimension = ProjectedCatalogDimension
|
|
947
1009
|
export type ProjectedPickerCatalog = ProjectedCatalog
|
|
948
1010
|
|
|
949
|
-
function projectOption(o: PickerOption, detail: PickerCatalogDetail): ProjectedPickerOption {
|
|
1011
|
+
function projectOption(o: PickerOption, detail: PickerCatalogDetail, imageUrl?: string): ProjectedPickerOption {
|
|
950
1012
|
// `term` rides at BOTH detail levels: a thin client renders `label` and
|
|
951
1013
|
// injects `term`, so compact must carry it — it is what makes compact hint
|
|
952
1014
|
// mode possible without a second round-trip for the full payload.
|
|
1015
|
+
const projected = projectOptionFields(o, detail, imageUrl)
|
|
1016
|
+
// Per-option params (a wipe's direction) ride at both detail levels too: an
|
|
1017
|
+
// id-only client needs the rows to offer the control at all.
|
|
1018
|
+
return o.params
|
|
1019
|
+
? {
|
|
1020
|
+
...projected,
|
|
1021
|
+
params: o.params.map((d) => ({
|
|
1022
|
+
field: d.field,
|
|
1023
|
+
label: d.label,
|
|
1024
|
+
options: d.options.map((p) => projectOption(p, detail)),
|
|
1025
|
+
})),
|
|
1026
|
+
}
|
|
1027
|
+
: projected
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function projectOptionFields(o: PickerOption, detail: PickerCatalogDetail, imageUrl?: string): ProjectedPickerOption {
|
|
1031
|
+
// `imageUrl` (display data) rides at both detail levels too, present only
|
|
1032
|
+
// when the option has a picture.
|
|
1033
|
+
const extras = { ...(o.motion ? { motion: o.motion } : {}), ...(imageUrl ? { imageUrl } : {}) }
|
|
953
1034
|
return detail === "full"
|
|
954
1035
|
? {
|
|
955
1036
|
id: o.id,
|
|
@@ -959,9 +1040,25 @@ function projectOption(o: PickerOption, detail: PickerCatalogDetail): ProjectedP
|
|
|
959
1040
|
promptHint: o.promptHint,
|
|
960
1041
|
term: o.term,
|
|
961
1042
|
icon: o.icon,
|
|
962
|
-
...
|
|
1043
|
+
...extras,
|
|
963
1044
|
}
|
|
964
|
-
: { id: o.id, label: o.label, category: o.category, term: o.term, icon: o.icon, ...
|
|
1045
|
+
: { id: o.id, label: o.label, category: o.category, term: o.term, icon: o.icon, ...extras }
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* The topics a catalog's settings are grouped under in the editor (Person,
|
|
1050
|
+
* Styling), as node-data fields; only the fields the catalog has are listed.
|
|
1051
|
+
*/
|
|
1052
|
+
function catalogSections(c: PickerCatalog, images: PickerImageOptions | undefined): ProjectedCatalogSection[] | undefined {
|
|
1053
|
+
const source = catalogTopics(c.catalogId)
|
|
1054
|
+
if (!source) return undefined
|
|
1055
|
+
const present = new Set((c.dimensions ?? []).map((d) => d.field))
|
|
1056
|
+
return source
|
|
1057
|
+
.map((s) => {
|
|
1058
|
+
const imageUrl = images ? pickerSectionImageUrl(s.label, images) : undefined
|
|
1059
|
+
return { label: s.label, fields: s.fields.filter((f) => present.has(f)), ...(imageUrl ? { imageUrl } : {}) }
|
|
1060
|
+
})
|
|
1061
|
+
.filter((s) => s.fields.length > 0)
|
|
965
1062
|
}
|
|
966
1063
|
|
|
967
1064
|
/** Project a catalog to the wire shape: compact by default, optional category/field filter. */
|
|
@@ -981,17 +1078,20 @@ export function projectPickerCatalog(
|
|
|
981
1078
|
categoryLabels: c.categoryLabels,
|
|
982
1079
|
detail,
|
|
983
1080
|
}
|
|
1081
|
+
const images = opts.images
|
|
1082
|
+
const imageOf = (field: string | undefined, o: PickerOption) =>
|
|
1083
|
+
images ? pickerOptionImageUrl(c, field, o.id, images) : undefined
|
|
984
1084
|
const projectDims = (dims: readonly PickerDimension[]) =>
|
|
985
1085
|
dims.map((d) => ({
|
|
986
1086
|
field: d.field,
|
|
987
1087
|
label: d.label,
|
|
988
|
-
options: d.options.map((o) => projectOption(o, detail)),
|
|
1088
|
+
options: d.options.map((o) => projectOption(o, detail, imageOf(d.field, o))),
|
|
989
1089
|
}))
|
|
990
1090
|
|
|
991
1091
|
if (c.kind === "single") {
|
|
992
1092
|
let options = c.options ?? []
|
|
993
1093
|
if (opts.category) options = options.filter((o) => o.category === opts.category)
|
|
994
|
-
const projected = { ...base, options: options.map((o) => projectOption(o, detail)) }
|
|
1094
|
+
const projected = { ...base, options: options.map((o) => projectOption(o, detail, imageOf(c.valueField, o))) }
|
|
995
1095
|
// A single-dim catalog may still carry secondary parameter dimensions
|
|
996
1096
|
// (transition's position/duration/intensity). They are additive — a
|
|
997
1097
|
// consumer reading only `options` is unaffected — but dropping them here
|
|
@@ -1003,7 +1103,8 @@ export function projectPickerCatalog(
|
|
|
1003
1103
|
}
|
|
1004
1104
|
let dims = c.dimensions ?? []
|
|
1005
1105
|
if (opts.field) dims = dims.filter((d) => d.field === opts.field)
|
|
1006
|
-
|
|
1106
|
+
const sections = catalogSections(c, images)
|
|
1107
|
+
return { ...base, fields: c.fields, dimensions: projectDims(dims), ...(sections ? { sections } : {}) }
|
|
1007
1108
|
}
|
|
1008
1109
|
|
|
1009
1110
|
/**
|
|
@@ -1011,6 +1112,6 @@ export function projectPickerCatalog(
|
|
|
1011
1112
|
* `/v1/catalogs` wire shape. This is what the `GET /v1/catalogs` route and its
|
|
1012
1113
|
* SDK resource serve, so a deployment's vendored packs are reflected verbatim.
|
|
1013
1114
|
*/
|
|
1014
|
-
export function projectAllCatalogs(opts: { detail?: PickerCatalogDetail } = {}): ProjectedCatalog[] {
|
|
1015
|
-
return getRegisteredPickerCatalogs().map((c) => projectPickerCatalog(c, { detail: opts.detail }))
|
|
1115
|
+
export function projectAllCatalogs(opts: { detail?: PickerCatalogDetail; images?: PickerImageOptions } = {}): ProjectedCatalog[] {
|
|
1116
|
+
return getRegisteredPickerCatalogs().map((c) => projectPickerCatalog(c, { detail: opts.detail, images: opts.images }))
|
|
1016
1117
|
}
|
package/src/prompt-builder.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* provider-aware prompt truncation (default 5000 chars), and reference image filtering by model support.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { joinSentences } from "./hint-join.js"
|
|
7
8
|
import { resolveTemplate, applyTemplate } from "./prompt-templates.js"
|
|
8
9
|
import { NATIVE_NEGATIVE_PROMPT_MODELS, MODELS_WITH_REFERENCE_IMAGE_SUPPORT, imageReferenceLimit, getMaxImagePromptChars, getMaxNegativePromptChars } from "@nodaro/shared"
|
|
9
10
|
import { getStylePromptHint, isDeniedStyleId } from "./style.js"
|
|
@@ -60,7 +61,7 @@ function composeIdentityDescPart(
|
|
|
60
61
|
if (c) parts.push(c)
|
|
61
62
|
const e = elementInjection?.trim()
|
|
62
63
|
if (e) parts.push(e)
|
|
63
|
-
return parts.length > 0 ? `${subject} — ${parts
|
|
64
|
+
return parts.length > 0 ? `${subject} — ${joinSentences(parts)}` : subject
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
/**
|
package/src/prompt-hint-join.ts
CHANGED
|
@@ -22,7 +22,11 @@
|
|
|
22
22
|
* Never mutates its inputs.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
/** The measured separator between the user's prompt and each folded hint.
|
|
25
|
+
/** The measured separator between the user's prompt and each folded hint.
|
|
26
|
+
* Pieces that already end a sentence are followed by a single space instead
|
|
27
|
+
* (`joinSentences`), so this is the longest separator a join can add. */
|
|
28
|
+
import { joinSentences } from "./hint-join.js"
|
|
29
|
+
|
|
26
30
|
export const PROMPT_HINT_SEPARATOR = ". "
|
|
27
31
|
|
|
28
32
|
/**
|
|
@@ -35,5 +39,7 @@ export const PROMPT_HINT_SEPARATOR = ". "
|
|
|
35
39
|
*/
|
|
36
40
|
export function joinPromptHints(userPrompt: string, hints: readonly string[]): string {
|
|
37
41
|
if (hints.length === 0) return userPrompt
|
|
38
|
-
|
|
42
|
+
// Sentence-aware: a prompt or hint that already ends a sentence is followed
|
|
43
|
+
// by a space, not by PROMPT_HINT_SEPARATOR (which made "watermark.. hint").
|
|
44
|
+
return joinSentences([userPrompt.trim(), ...hints])
|
|
39
45
|
}
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
* section is written flush-left rather than relying on that collapse to be
|
|
46
46
|
* harmless.
|
|
47
47
|
*/
|
|
48
|
-
import { joinPromptHints
|
|
48
|
+
import { joinPromptHints } from "./prompt-hint-join.js"
|
|
49
|
+
import { joinSentences } from "./hint-join.js"
|
|
49
50
|
import {
|
|
50
51
|
renderDirectionHintClauses,
|
|
51
52
|
type DirectionFamily,
|
|
@@ -173,10 +174,7 @@ export function partitionStyleClauses(
|
|
|
173
174
|
*/
|
|
174
175
|
export function styleSectionFromClauses(clauses: readonly SlottedPromptClause[]): string {
|
|
175
176
|
const line = (slot: PromptClauseSlot) =>
|
|
176
|
-
clauses
|
|
177
|
-
.filter((c) => c.slot === slot)
|
|
178
|
-
.map((c) => c.text)
|
|
179
|
-
.join(PROMPT_HINT_SEPARATOR)
|
|
177
|
+
joinSentences(clauses.filter((c) => c.slot === slot).map((c) => c.text))
|
|
180
178
|
const lines = [line("film"), line("scene")].filter((l) => l.length > 0)
|
|
181
179
|
return lines.length === 0 ? "" : `${STYLE_SECTION_HEADER}\n${lines.join("\n")}`
|
|
182
180
|
}
|
package/src/resolve-prompt.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolveNodeRefs, readPromptAffixes, type PromptAffixes } from "@nodaro/shared"
|
|
2
2
|
import { SOCIAL_POST_NODE_TYPES } from "@nodaro/shared"
|
|
3
|
+
import { joinSentences } from "./hint-join.js"
|
|
3
4
|
|
|
4
5
|
export interface ResolvePromptArgs {
|
|
5
6
|
override?: string
|
|
@@ -72,7 +73,8 @@ export function applyPromptAffixes(
|
|
|
72
73
|
* override (list fan-out) > first present typed candidate > wired > "".
|
|
73
74
|
* "present" = non-empty after trim. {Label} refs are resolved on the chosen
|
|
74
75
|
* branch via the shared resolveNodeRefs. With `appendWired`, the chosen base
|
|
75
|
-
* AND the wired value are both emitted
|
|
76
|
+
* AND the wired value are both emitted, joined as sentences (`joinSentences`:
|
|
77
|
+
* ". ", or a space after one that already ends). Affixes wrap the result. */
|
|
76
78
|
export function resolvePrompt({ override, typed = [], wired, refMap, appendWired, affixes }: ResolvePromptArgs): string {
|
|
77
79
|
return applyPromptAffixes(resolveCore({ override, typed, wired, refMap, appendWired }), affixes, refMap)
|
|
78
80
|
}
|
|
@@ -83,7 +85,7 @@ function resolveCore({ override, typed = [], wired, refMap, appendWired }: Omit<
|
|
|
83
85
|
// so per-item fan-out prompts are unchanged.
|
|
84
86
|
if (appendWired && !present(override)) {
|
|
85
87
|
const base = typed.find(present)
|
|
86
|
-
return [base, wired].filter(present).map((s) => rr(s, refMap))
|
|
88
|
+
return joinSentences([base, wired].filter(present).map((s) => rr(s, refMap)))
|
|
87
89
|
}
|
|
88
90
|
if (present(override)) return rr(override, refMap)
|
|
89
91
|
for (const t of typed) if (present(t)) return rr(t, refMap)
|
|
@@ -92,13 +94,13 @@ function resolveCore({ override, typed = [], wired, refMap, appendWired }: Omit<
|
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
/** Compose a final NEGATIVE prompt from a TYPED base + a WIRED (connected
|
|
95
|
-
* negative-handle) value: both are emitted, joined
|
|
97
|
+
* negative-handle) value: both are emitted, joined as sentences (`joinSentences`, mirrors `appendWired`
|
|
96
98
|
* for the positive prompt). Pure join — the caller resolves `{label}` refs on
|
|
97
99
|
* the typed value first, and the wired value is already a resolved output that
|
|
98
100
|
* the input-resolver has filtered (referenced / Inject-Negative-off dropped).
|
|
99
101
|
* Generate-image / generate-video only; empty parts are dropped. */
|
|
100
102
|
export function composeNegative(typed?: string, wired?: string): string {
|
|
101
|
-
return [typed, wired]
|
|
103
|
+
return joinSentences([typed, wired])
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
/** Ordered typed-candidate fields per node type — the precedence source of
|
|
@@ -184,3 +186,19 @@ export function computeLlmChatFields(
|
|
|
184
186
|
systemPrompt: resolvePrompt({ typed: [data.systemPrompt as string | undefined], wired: wiredSystemPrompt, refMap }),
|
|
185
187
|
}
|
|
186
188
|
}
|
|
189
|
+
|
|
190
|
+
/** generate-script's topic. Both engines call this, so a wired Text node reads
|
|
191
|
+
* the same on a single-node run and a server run.
|
|
192
|
+
*
|
|
193
|
+
* Precedence is override (a list fan-out item) > wired > `data.prompt`. Wired
|
|
194
|
+
* comes BEFORE the typed field, the reverse of `computeNodePrompt`, because
|
|
195
|
+
* `data.prompt` has no editor: the panel's text field is the style guide. A
|
|
196
|
+
* value there was written by the old connect-time `{Label}` auto-fill or by an
|
|
197
|
+
* agent, and it is kept only as the fallback that saved workflows rely on. */
|
|
198
|
+
export function computeScriptTopic(
|
|
199
|
+
data: Record<string, unknown>,
|
|
200
|
+
{ override, wired, refMap }: Omit<ComputeNodePromptArgs, "appendWired">,
|
|
201
|
+
): string {
|
|
202
|
+
const core = [override, wired, data.prompt as string | undefined].find(present)
|
|
203
|
+
return applyPromptAffixes(core === undefined ? "" : rr(core, refMap), readPromptAffixes(data), refMap)
|
|
204
|
+
}
|
package/src/styling.ts
CHANGED
|
@@ -467,6 +467,24 @@ export const STYLING_DIMENSION_ORDER: ReadonlyArray<StylingDimension> = [
|
|
|
467
467
|
"wardrobe-state",
|
|
468
468
|
]
|
|
469
469
|
|
|
470
|
+
/**
|
|
471
|
+
* The Styling picker's topics, in display order (mirrors PERSON_DIMENSION_SECTIONS):
|
|
472
|
+
* every dimension belongs to exactly one — styling-sections.test.ts fails the build
|
|
473
|
+
* when a new dimension is left out or listed twice. The editor lays the open
|
|
474
|
+
* picker out by these, and the API serves them as the catalog's `sections`.
|
|
475
|
+
*/
|
|
476
|
+
export interface StylingDimensionSection {
|
|
477
|
+
readonly label: string
|
|
478
|
+
readonly dimensions: ReadonlyArray<StylingDimension>
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export const STYLING_DIMENSION_SECTIONS: ReadonlyArray<StylingDimensionSection> = [
|
|
482
|
+
{ label: "Beauty & Hair", dimensions: ["makeup", "hair-cut", "hair-treatment", "hair-state", "nails", "face-paint"] },
|
|
483
|
+
{ label: "Accessories", dimensions: ["eyewear", "headwear", "jewelry"] },
|
|
484
|
+
{ label: "Wardrobe", dimensions: ["outfit", "top", "bottom", "outerwear", "legwear", "footwear"] },
|
|
485
|
+
{ label: "Fabric & Fit", dimensions: ["fabric", "wardrobe-state"] },
|
|
486
|
+
]
|
|
487
|
+
|
|
470
488
|
export const STYLING_DIMENSION_LABELS: Readonly<Record<StylingDimension, string>> = {
|
|
471
489
|
makeup: "Makeup",
|
|
472
490
|
eyewear: "Eyewear",
|