@nodaro/prompts 1.38.0 → 1.40.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/chunk-2EW5B7ZV.cjs +86 -0
- package/dist/chunk-2EW5B7ZV.cjs.map +1 -0
- package/dist/chunk-2P26SZX5.cjs +414 -0
- package/dist/chunk-2P26SZX5.cjs.map +1 -0
- package/dist/{chunk-7XKHDXCL.cjs → chunk-3U7X6JZN.cjs} +13 -12
- package/dist/chunk-3U7X6JZN.cjs.map +1 -0
- package/dist/chunk-4GKTOVFM.cjs +8 -0
- package/dist/chunk-4GKTOVFM.cjs.map +1 -0
- package/dist/{chunk-HRBBNODV.js → chunk-6GVFJLDL.js} +4 -3
- package/dist/chunk-6GVFJLDL.js.map +1 -0
- package/dist/{chunk-LVSH7K7R.cjs → chunk-C3NHNECH.cjs} +165 -88
- package/dist/chunk-C3NHNECH.cjs.map +1 -0
- package/dist/{chunk-CEPVQQVO.js → chunk-C46256GZ.js} +43 -5
- package/dist/chunk-C46256GZ.js.map +1 -0
- package/dist/{chunk-JKCR6WRZ.cjs → chunk-DQFQJECU.cjs} +43 -5
- package/dist/chunk-DQFQJECU.cjs.map +1 -0
- package/dist/{chunk-QDEWBBUR.js → chunk-DUXX6CBX.js} +12 -143
- package/dist/chunk-DUXX6CBX.js.map +1 -0
- package/dist/chunk-EAYEZXWT.js +388 -0
- package/dist/chunk-EAYEZXWT.js.map +1 -0
- package/dist/chunk-GILUVQQJ.js +6 -0
- package/dist/chunk-GILUVQQJ.js.map +1 -0
- package/dist/{chunk-KIYNOOPJ.js → chunk-GTDKDD7Y.js} +12 -6
- package/dist/chunk-GTDKDD7Y.js.map +1 -0
- package/dist/{chunk-QBEZMR2F.js → chunk-HQTHDTHN.js} +137 -62
- package/dist/chunk-HQTHDTHN.js.map +1 -0
- package/dist/{chunk-TLVX5PXD.cjs → chunk-IUCNJAHW.cjs} +13 -7
- package/dist/chunk-IUCNJAHW.cjs.map +1 -0
- package/dist/{chunk-ZFIXQRBZ.cjs → chunk-LXPKTI2K.cjs} +12 -83
- package/dist/chunk-LXPKTI2K.cjs.map +1 -0
- package/dist/{chunk-CVPB2N3Z.cjs → chunk-LZ53F5BN.cjs} +103 -5
- package/dist/chunk-LZ53F5BN.cjs.map +1 -0
- package/dist/{chunk-4FDNVNJE.js → chunk-N6VMPG5S.js} +12 -79
- package/dist/chunk-N6VMPG5S.js.map +1 -0
- package/dist/{chunk-LK2CZK5U.cjs → chunk-PWWM7Q3T.cjs} +7 -7
- package/dist/{chunk-LK2CZK5U.cjs.map → chunk-PWWM7Q3T.cjs.map} +1 -1
- package/dist/{chunk-N7TOWXDJ.js → chunk-QDUTMOF4.js} +3 -3
- package/dist/{chunk-N7TOWXDJ.js.map → chunk-QDUTMOF4.js.map} +1 -1
- package/dist/{chunk-NO6XHX4R.js → chunk-RB2F5JQF.js} +100 -7
- package/dist/chunk-RB2F5JQF.js.map +1 -0
- package/dist/{chunk-MENBHEAY.cjs → chunk-WRM52ZBN.cjs} +19 -161
- package/dist/chunk-WRM52ZBN.cjs.map +1 -0
- package/dist/chunk-YS6HOWMW.js +80 -0
- package/dist/chunk-YS6HOWMW.js.map +1 -0
- package/dist/entries/assembly.cjs +34 -25
- package/dist/entries/assembly.d.cts +121 -37
- package/dist/entries/assembly.d.ts +121 -37
- package/dist/entries/assembly.js +5 -4
- package/dist/entries/doctrine.cjs +46 -46
- package/dist/entries/doctrine.d.cts +3 -3
- package/dist/entries/doctrine.d.ts +3 -3
- package/dist/entries/doctrine.js +1 -1
- package/dist/entries/picker-catalogs.cjs +42 -42
- package/dist/entries/picker-catalogs.js +3 -3
- package/dist/entries/presets.cjs +49 -26
- package/dist/entries/presets.d.cts +53 -1
- package/dist/entries/presets.d.ts +53 -1
- package/dist/entries/presets.js +4 -1
- package/dist/entries/prompt-builder.cjs +135 -98
- package/dist/entries/prompt-builder.d.cts +22 -11
- package/dist/entries/prompt-builder.d.ts +22 -11
- package/dist/entries/prompt-builder.js +4 -3
- package/dist/entries/prompt-fields.cjs +41 -40
- package/dist/entries/prompt-fields.d.cts +2 -1
- package/dist/entries/prompt-fields.d.ts +2 -1
- package/dist/entries/prompt-fields.js +2 -1
- package/dist/index.cjs +339 -273
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +11 -9
- package/dist/{subject-registry-BUswuEVD.d.cts → subject-registry-CQi_CZfJ.d.cts} +78 -9
- package/dist/{subject-registry-D7V8PEG-.d.ts → subject-registry-DFiGx49v.d.ts} +78 -9
- package/package.json +2 -2
- package/src/__tests__/assemble-image-input-style-references.test.ts +173 -0
- package/src/__tests__/catalog-id-guard.test.ts +30 -0
- package/src/__tests__/factory-preset-previews.test.ts +123 -0
- package/src/__tests__/factory-preset-references.test.ts +222 -0
- package/src/__tests__/factory-presets-prototype-keys.test.ts +43 -0
- package/src/__tests__/frame-reference-slots.test.ts +43 -0
- package/src/__tests__/gemini-omni-inputs.test.ts +12 -10
- package/src/__tests__/provider-prompt-doctrine.test.ts +14 -0
- package/src/__tests__/user-direction-refs.test.ts +180 -0
- package/src/__tests__/veo-i2v-inputs.test.ts +16 -11
- package/src/assemble-image-input.ts +114 -13
- package/src/catalog-id-guard.ts +18 -2
- package/src/color-names.ts +192 -0
- package/src/direction-registry.ts +66 -5
- package/src/entries/assembly.ts +1 -0
- package/src/entries/prompt-builder.ts +2 -0
- package/src/factory-presets/generate-image.ts +29 -0
- package/src/factory-presets/index.ts +6 -1
- package/src/factory-presets/preview-files.generated.ts +4 -0
- package/src/factory-presets/previews.ts +40 -0
- package/src/factory-presets/references.ts +81 -0
- package/src/factory-presets/shared-image.ts +4 -0
- package/src/factory-presets/types.ts +8 -0
- package/src/frame-reference-slots.ts +92 -0
- package/src/gemini-omni-inputs.ts +23 -22
- package/src/image-reference-doctrine.ts +3 -3
- package/src/image-token-pattern.ts +13 -0
- package/src/node-prompt-fields.ts +5 -3
- package/src/own-entry.ts +11 -0
- package/src/prompt-builder.ts +4 -5
- package/src/prompt-style-section.ts +3 -3
- package/src/prompt-wizard-categories.ts +2 -0
- package/src/provider-prompt-doctrine.ts +45 -3
- package/src/ref-binding.ts +28 -3
- package/src/resolve-prompt.ts +2 -1
- package/src/seedance-2-inputs.ts +15 -40
- package/src/user-direction-refs.ts +110 -0
- package/src/veo-i2v-inputs.ts +24 -17
- package/src/video-reference-resolver.ts +1 -1
- package/dist/chunk-4FDNVNJE.js.map +0 -1
- package/dist/chunk-7XKHDXCL.cjs.map +0 -1
- package/dist/chunk-CEPVQQVO.js.map +0 -1
- package/dist/chunk-CVPB2N3Z.cjs.map +0 -1
- package/dist/chunk-HRBBNODV.js.map +0 -1
- package/dist/chunk-JKCR6WRZ.cjs.map +0 -1
- package/dist/chunk-KIYNOOPJ.js.map +0 -1
- package/dist/chunk-LVSH7K7R.cjs.map +0 -1
- package/dist/chunk-MENBHEAY.cjs.map +0 -1
- package/dist/chunk-NO6XHX4R.js.map +0 -1
- package/dist/chunk-QBEZMR2F.js.map +0 -1
- package/dist/chunk-QDEWBBUR.js.map +0 -1
- package/dist/chunk-TLVX5PXD.cjs.map +0 -1
- package/dist/chunk-UJKFHPZ7.js +0 -37
- package/dist/chunk-UJKFHPZ7.js.map +0 -1
- package/dist/chunk-W5SCZIOB.cjs +0 -43
- package/dist/chunk-W5SCZIOB.cjs.map +0 -1
- package/dist/chunk-ZFIXQRBZ.cjs.map +0 -1
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain colour names for a hex value — what a saved colour palette reads as
|
|
3
|
+
* in a prompt ("deep teal #0F4C5C"). A model follows a named colour more
|
|
4
|
+
* reliably than a bare hex code, and the hex keeps the exact value.
|
|
5
|
+
*
|
|
6
|
+
* NEAREST NAME, NOT A CLASSIFIER: a fixed table of everyday colour names, each
|
|
7
|
+
* with a representative value, and the one closest to the input in CIE Lab
|
|
8
|
+
* (ΔE 1976 — Euclidean distance in Lab, which tracks perceived difference far
|
|
9
|
+
* better than RGB). Deterministic, dependency-free, and a name is always one a
|
|
10
|
+
* person would say. Ties go to the earlier row.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Everyday colour names and a representative sRGB value for each. */
|
|
14
|
+
const NAMED: ReadonlyArray<readonly [name: string, hex: string]> = [
|
|
15
|
+
// Neutrals
|
|
16
|
+
["black", "#000000"],
|
|
17
|
+
["charcoal", "#36454F"],
|
|
18
|
+
["dark gray", "#555555"],
|
|
19
|
+
["slate gray", "#708090"],
|
|
20
|
+
["gray", "#808080"],
|
|
21
|
+
["warm gray", "#9A9088"],
|
|
22
|
+
["silver", "#C0C0C0"],
|
|
23
|
+
["light gray", "#D3D3D3"],
|
|
24
|
+
["off-white", "#F2F0EB"],
|
|
25
|
+
["white", "#FFFFFF"],
|
|
26
|
+
["ivory", "#FFFFF0"],
|
|
27
|
+
["cream", "#FFFDD0"],
|
|
28
|
+
["eggshell", "#F0EAD6"],
|
|
29
|
+
["linen", "#FAF0E6"],
|
|
30
|
+
["champagne", "#F7E7CE"],
|
|
31
|
+
["wheat", "#F5DEB3"],
|
|
32
|
+
// Earth tones and browns
|
|
33
|
+
["sand", "#C2B280"],
|
|
34
|
+
["tan", "#D2B48C"],
|
|
35
|
+
["khaki", "#C3B091"],
|
|
36
|
+
["taupe", "#8B8589"],
|
|
37
|
+
["camel", "#C19A6B"],
|
|
38
|
+
["caramel", "#C68E17"],
|
|
39
|
+
["brown", "#964B00"],
|
|
40
|
+
["chocolate", "#7B3F00"],
|
|
41
|
+
["coffee", "#6F4E37"],
|
|
42
|
+
["espresso", "#4E312D"],
|
|
43
|
+
["walnut", "#773F1A"],
|
|
44
|
+
["chestnut", "#954535"],
|
|
45
|
+
["mahogany", "#C04000"],
|
|
46
|
+
["rust", "#B7410E"],
|
|
47
|
+
["terracotta", "#E2725B"],
|
|
48
|
+
["copper", "#B87333"],
|
|
49
|
+
["bronze", "#CD7F32"],
|
|
50
|
+
["ochre", "#CC7722"],
|
|
51
|
+
// Oranges
|
|
52
|
+
["burnt orange", "#CC5500"],
|
|
53
|
+
["orange", "#FFA500"],
|
|
54
|
+
["tangerine", "#F28500"],
|
|
55
|
+
["apricot", "#FBCEB1"],
|
|
56
|
+
["peach", "#FFCBA4"],
|
|
57
|
+
["coral", "#FF7F50"],
|
|
58
|
+
["salmon", "#FA8072"],
|
|
59
|
+
// Reds
|
|
60
|
+
["red", "#FF0000"],
|
|
61
|
+
["scarlet", "#FF2400"],
|
|
62
|
+
["crimson", "#DC143C"],
|
|
63
|
+
["cherry red", "#D2042D"],
|
|
64
|
+
["brick red", "#CB4154"],
|
|
65
|
+
["burgundy", "#800020"],
|
|
66
|
+
["maroon", "#800000"],
|
|
67
|
+
["wine red", "#722F37"],
|
|
68
|
+
["oxblood", "#4A0000"],
|
|
69
|
+
// Pinks
|
|
70
|
+
["raspberry", "#E30B5C"],
|
|
71
|
+
["rose", "#FF007F"],
|
|
72
|
+
["hot pink", "#FF69B4"],
|
|
73
|
+
["pink", "#FFC0CB"],
|
|
74
|
+
["blush pink", "#E8B4B8"],
|
|
75
|
+
["dusty rose", "#C08081"],
|
|
76
|
+
["mauve", "#B784A7"],
|
|
77
|
+
// Purples
|
|
78
|
+
["magenta", "#FF00FF"],
|
|
79
|
+
["orchid", "#DA70D6"],
|
|
80
|
+
["plum", "#8E4585"],
|
|
81
|
+
["eggplant", "#614051"],
|
|
82
|
+
["dusty purple", "#6E5A7E"],
|
|
83
|
+
["lilac", "#C8A2C8"],
|
|
84
|
+
["lavender", "#B57EDC"],
|
|
85
|
+
["pale lavender", "#E6E6FA"],
|
|
86
|
+
["amethyst", "#9966CC"],
|
|
87
|
+
["violet", "#8F00FF"],
|
|
88
|
+
["purple", "#800080"],
|
|
89
|
+
["indigo", "#4B0082"],
|
|
90
|
+
// Blues
|
|
91
|
+
["navy blue", "#000080"],
|
|
92
|
+
["midnight blue", "#191970"],
|
|
93
|
+
["prussian blue", "#003153"],
|
|
94
|
+
["dusky blue", "#4B5E7D"],
|
|
95
|
+
["royal blue", "#4169E1"],
|
|
96
|
+
["cobalt blue", "#0047AB"],
|
|
97
|
+
["blue", "#0000FF"],
|
|
98
|
+
["cornflower blue", "#6495ED"],
|
|
99
|
+
["denim blue", "#1560BD"],
|
|
100
|
+
["steel blue", "#4682B4"],
|
|
101
|
+
["slate blue", "#6A5ACD"],
|
|
102
|
+
["periwinkle", "#CCCCFF"],
|
|
103
|
+
["sky blue", "#87CEEB"],
|
|
104
|
+
["baby blue", "#89CFF0"],
|
|
105
|
+
["powder blue", "#B0E0E6"],
|
|
106
|
+
["ice blue", "#D6ECEF"],
|
|
107
|
+
["azure", "#007FFF"],
|
|
108
|
+
["cerulean", "#007BA7"],
|
|
109
|
+
["petrol blue", "#005F6A"],
|
|
110
|
+
// Cyans and teals
|
|
111
|
+
["cyan", "#00FFFF"],
|
|
112
|
+
["turquoise", "#40E0D0"],
|
|
113
|
+
["aquamarine", "#7FFFD4"],
|
|
114
|
+
["teal", "#008080"],
|
|
115
|
+
["dark teal", "#014D4E"],
|
|
116
|
+
["dark slate gray", "#2F4F4F"],
|
|
117
|
+
// Greens
|
|
118
|
+
["sea green", "#2E8B57"],
|
|
119
|
+
["emerald green", "#50C878"],
|
|
120
|
+
["jade green", "#00A86B"],
|
|
121
|
+
["mint green", "#98FF98"],
|
|
122
|
+
["seafoam green", "#93E9BE"],
|
|
123
|
+
["sage green", "#9CAF88"],
|
|
124
|
+
["olive green", "#708238"],
|
|
125
|
+
["olive", "#808000"],
|
|
126
|
+
["moss green", "#8A9A5B"],
|
|
127
|
+
["forest green", "#228B22"],
|
|
128
|
+
["hunter green", "#355E3B"],
|
|
129
|
+
["bottle green", "#006A4E"],
|
|
130
|
+
["dark green", "#013220"],
|
|
131
|
+
["green", "#008000"],
|
|
132
|
+
["kelly green", "#4CBB17"],
|
|
133
|
+
["lime green", "#32CD32"],
|
|
134
|
+
["chartreuse", "#7FFF00"],
|
|
135
|
+
["pistachio", "#93C572"],
|
|
136
|
+
// Yellows and golds
|
|
137
|
+
["yellow", "#FFFF00"],
|
|
138
|
+
["lemon yellow", "#FFF44F"],
|
|
139
|
+
["pale yellow", "#FDFD96"],
|
|
140
|
+
["canary yellow", "#FFEF00"],
|
|
141
|
+
["mustard yellow", "#E1AD01"],
|
|
142
|
+
["gold", "#FFD700"],
|
|
143
|
+
["amber", "#FFBF00"],
|
|
144
|
+
["honey", "#F0A830"],
|
|
145
|
+
["saffron", "#F4C430"],
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
const HEX_RE = /^#[0-9a-f]{6}$/i
|
|
149
|
+
|
|
150
|
+
type Lab = readonly [l: number, a: number, b: number]
|
|
151
|
+
|
|
152
|
+
function channel(v: number): number {
|
|
153
|
+
const c = v / 255
|
|
154
|
+
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function labPivot(t: number): number {
|
|
158
|
+
return t > 216 / 24389 ? Math.cbrt(t) : (24389 / 27 * t + 16) / 116
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** sRGB hex → CIE Lab (D65). Only called with a value that passed `HEX_RE`. */
|
|
162
|
+
function toLab(hex: string): Lab {
|
|
163
|
+
const r = channel(parseInt(hex.slice(1, 3), 16))
|
|
164
|
+
const g = channel(parseInt(hex.slice(3, 5), 16))
|
|
165
|
+
const b = channel(parseInt(hex.slice(5, 7), 16))
|
|
166
|
+
const x = labPivot((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / 0.95047)
|
|
167
|
+
const y = labPivot(0.2126729 * r + 0.7151522 * g + 0.072175 * b)
|
|
168
|
+
const z = labPivot((0.0193339 * r + 0.119192 * g + 0.9503041 * b) / 1.08883)
|
|
169
|
+
return [116 * y - 16, 500 * (x - y), 200 * (y - z)]
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let namedLab: ReadonlyArray<readonly [string, Lab]> | null = null
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The everyday name nearest to `hex` (`#RRGGBB`, any case), or `""` when the
|
|
176
|
+
* value is not a `#RRGGBB` colour.
|
|
177
|
+
*/
|
|
178
|
+
export function nearestColorName(hex: string): string {
|
|
179
|
+
if (!HEX_RE.test(hex)) return ""
|
|
180
|
+
namedLab ??= NAMED.map(([name, value]) => [name, toLab(value)] as const)
|
|
181
|
+
const [l, a, b] = toLab(hex)
|
|
182
|
+
let best = ""
|
|
183
|
+
let bestDistance = Infinity
|
|
184
|
+
for (const [name, [nl, na, nb]] of namedLab) {
|
|
185
|
+
const d = (l - nl) ** 2 + (a - na) ** 2 + (b - nb) ** 2
|
|
186
|
+
if (d < bestDistance) {
|
|
187
|
+
bestDistance = d
|
|
188
|
+
best = name
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return best
|
|
192
|
+
}
|
|
@@ -69,6 +69,7 @@ import { buildActionFxHints } from "./action-fx.js"
|
|
|
69
69
|
import { getTemporalPromptHint, getTemporalTerm } from "./temporal.js"
|
|
70
70
|
import { renderTransitionBases } from "./transitions.js"
|
|
71
71
|
import { getLoopSubjectPromptHint, getLoopSubjectTerm } from "./loop-subject.js"
|
|
72
|
+
import { parseUserDirectionRef, type UserDirectionRefKind } from "./user-direction-refs.js"
|
|
72
73
|
|
|
73
74
|
/** Which generation stages fold a dimension. */
|
|
74
75
|
export type DirectionSurface = "image" | "video" | "both"
|
|
@@ -112,6 +113,14 @@ export interface DirectionFieldSpec {
|
|
|
112
113
|
readonly styleGroup?: DirectionStyleGroup
|
|
113
114
|
/** Ids honored per dimension. Extras are SLICED at render, never a 400. */
|
|
114
115
|
readonly maxPicks: number
|
|
116
|
+
/**
|
|
117
|
+
* The kind of the caller's OWN saved resource this row also takes, as
|
|
118
|
+
* `<kind>:<uuid>` (`user-direction-refs.ts`) — a palette on `colorLook`, a
|
|
119
|
+
* moodboard on `style`. Only single-pick rows carry one, so a pick budget can
|
|
120
|
+
* never slice a reference away. The platform resolves the reference before
|
|
121
|
+
* the fold and hands the rendered clause in as `refs`.
|
|
122
|
+
*/
|
|
123
|
+
readonly userRef?: UserDirectionRefKind
|
|
115
124
|
/**
|
|
116
125
|
* Render selected ids in this catalog's own doctrine: per-id for independent
|
|
117
126
|
* catalogs, ONE blended clause for Mood / Aesthetic / Photographer, the
|
|
@@ -228,12 +237,12 @@ export const DIRECTION_FIELDS = [
|
|
|
228
237
|
{ key: "lightingDirection", surface: "both", family: "look", maxPicks: 1, render: lighting },
|
|
229
238
|
{ key: "lightingRatio", surface: "both", family: "look", maxPicks: 1, render: lighting },
|
|
230
239
|
{ key: "colorTemperature", surface: "both", family: "look", maxPicks: 1, render: lighting },
|
|
231
|
-
{ key: "colorLook", surface: "both", family: "look", styleGroup: "film", maxPicks: 1, render: perId(getColorLookPromptHint, getColorLookTerm) },
|
|
240
|
+
{ key: "colorLook", surface: "both", family: "look", styleGroup: "film", maxPicks: 1, userRef: "palette", render: perId(getColorLookPromptHint, getColorLookTerm) },
|
|
232
241
|
{ key: "atmosphere", surface: "both", family: "look", maxPicks: 2, render: viaListBuilder(buildAtmosphereHints) },
|
|
233
242
|
{ key: "postProcess", surface: "image", family: "look", maxPicks: 2, render: viaListBuilder(buildPostProcessHints) },
|
|
234
243
|
|
|
235
244
|
// Style.
|
|
236
|
-
{ key: "style", surface: "both", family: "look", styleGroup: "film", maxPicks: 1, render: perId(getStylePromptHint, getStyleTerm) },
|
|
245
|
+
{ key: "style", surface: "both", family: "look", styleGroup: "film", maxPicks: 1, userRef: "moodboard", render: perId(getStylePromptHint, getStyleTerm) },
|
|
237
246
|
{ key: "mood", surface: "both", family: "look", maxPicks: 2, render: viaMood },
|
|
238
247
|
{ key: "aesthetic", surface: "both", family: "look", maxPicks: 2, render: viaStringBuilder(buildAestheticHints) },
|
|
239
248
|
{ key: "photoGenre", surface: "image", family: "look", maxPicks: 1, render: perId(getPhotoGenrePromptHint, getPhotoGenreTerm) },
|
|
@@ -303,11 +312,39 @@ export const FILM_STYLE_KEYS: ReadonlyArray<DirectionKey> = DIRECTION_SPECS.filt
|
|
|
303
312
|
(f) => f.styleGroup === "film",
|
|
304
313
|
).map((f) => f.key as DirectionKey)
|
|
305
314
|
|
|
315
|
+
/**
|
|
316
|
+
* The direction key that takes a reference of this kind — `colorLook` for a
|
|
317
|
+
* palette, `style` for a moodboard. Derived from the table's `userRef` column.
|
|
318
|
+
*/
|
|
319
|
+
export function directionKeyForUserRef(kind: UserDirectionRefKind): DirectionKey {
|
|
320
|
+
const row = DIRECTION_SPECS.find((f) => f.userRef === kind)
|
|
321
|
+
if (!row) throw new Error(`no direction row takes a ${kind} reference`)
|
|
322
|
+
return row.key as DirectionKey
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** The kind of saved resource a direction key takes, or undefined (most keys). */
|
|
326
|
+
export function userRefKindOfDirectionKey(key: string): UserDirectionRefKind | undefined {
|
|
327
|
+
return DIRECTION_SPECS.find((f) => f.key === key)?.userRef
|
|
328
|
+
}
|
|
329
|
+
|
|
306
330
|
/** Verbosity for a whole fold, or split per family. */
|
|
307
331
|
export type DirectionHintMode =
|
|
308
332
|
| PickerHintMode
|
|
309
333
|
| { readonly look: PickerHintMode; readonly motion: PickerHintMode }
|
|
310
334
|
|
|
335
|
+
/** How a direction bag is folded. */
|
|
336
|
+
export interface DirectionRenderOptions {
|
|
337
|
+
readonly surface: "image" | "video"
|
|
338
|
+
readonly mode?: DirectionHintMode
|
|
339
|
+
/**
|
|
340
|
+
* Rendered clauses for the caller's OWN saved resources, keyed by the
|
|
341
|
+
* direction value (`palette:<id>` → `color palette: …`), resolved by the
|
|
342
|
+
* platform before the fold. A reference absent from the map renders nothing,
|
|
343
|
+
* exactly like an unknown catalog id. Absent = no reference resolves.
|
|
344
|
+
*/
|
|
345
|
+
readonly refs?: ReadonlyMap<string, string>
|
|
346
|
+
}
|
|
347
|
+
|
|
311
348
|
/** Image policy: full clause for every dimension (today's behavior). */
|
|
312
349
|
export const IMAGE_HINT_MODE_DEFAULT: DirectionHintMode = "full"
|
|
313
350
|
|
|
@@ -360,6 +397,30 @@ function normalizeDirectionIds(value: unknown, maxPicks: number): string[] {
|
|
|
360
397
|
return ids.slice(0, maxPicks)
|
|
361
398
|
}
|
|
362
399
|
|
|
400
|
+
/**
|
|
401
|
+
* One row's clauses. A row with no user-owned reference among its ids renders
|
|
402
|
+
* through its catalog doctrine exactly as before (byte parity). On a row that
|
|
403
|
+
* takes a reference (`userRef`), each id renders on its own, in order: a
|
|
404
|
+
* reference of the row's kind becomes its resolved clause from `refs` (nothing
|
|
405
|
+
* when unresolved), any other value goes through the catalog.
|
|
406
|
+
*/
|
|
407
|
+
function renderRow(
|
|
408
|
+
spec: DirectionFieldSpec,
|
|
409
|
+
ids: readonly string[],
|
|
410
|
+
mode: PickerHintMode,
|
|
411
|
+
refs: ReadonlyMap<string, string> | undefined,
|
|
412
|
+
): string[] {
|
|
413
|
+
if (spec.userRef === undefined || !ids.some((id) => parseUserDirectionRef(id) !== null)) {
|
|
414
|
+
return spec.render(ids, mode)
|
|
415
|
+
}
|
|
416
|
+
return ids.flatMap((id) => {
|
|
417
|
+
const ref = parseUserDirectionRef(id)
|
|
418
|
+
if (ref === null) return spec.render([id], mode)
|
|
419
|
+
const clause = ref.kind === spec.userRef ? refs?.get(id) : undefined
|
|
420
|
+
return clause ? [clause] : []
|
|
421
|
+
})
|
|
422
|
+
}
|
|
423
|
+
|
|
363
424
|
/** The rows a given generation stage folds, in table order. */
|
|
364
425
|
export function directionFieldsForSurface(
|
|
365
426
|
surface: "image" | "video",
|
|
@@ -383,7 +444,7 @@ export interface DirectionHintClause {
|
|
|
383
444
|
*/
|
|
384
445
|
export function renderDirectionHintClauses(
|
|
385
446
|
direction: DirectionFields | undefined,
|
|
386
|
-
opts:
|
|
447
|
+
opts: DirectionRenderOptions,
|
|
387
448
|
): DirectionHintClause[] {
|
|
388
449
|
if (!direction) return []
|
|
389
450
|
const mode = opts.mode ?? "full"
|
|
@@ -396,7 +457,7 @@ export function renderDirectionHintClauses(
|
|
|
396
457
|
spec.maxPicks,
|
|
397
458
|
)
|
|
398
459
|
if (ids.length === 0) continue
|
|
399
|
-
for (const hint of spec
|
|
460
|
+
for (const hint of renderRow(spec, ids, modeForFamily(mode, spec.family), opts.refs)) {
|
|
400
461
|
if (hint.length > 0 && !seen.has(hint)) {
|
|
401
462
|
seen.add(hint)
|
|
402
463
|
out.push({
|
|
@@ -436,7 +497,7 @@ export function renderDirectionHintClauses(
|
|
|
436
497
|
*/
|
|
437
498
|
export function renderDirectionHints(
|
|
438
499
|
direction: DirectionFields | undefined,
|
|
439
|
-
opts:
|
|
500
|
+
opts: DirectionRenderOptions,
|
|
440
501
|
): string[] {
|
|
441
502
|
return renderDirectionHintClauses(direction, opts).map((c) => c.text)
|
|
442
503
|
}
|
package/src/entries/assembly.ts
CHANGED
|
@@ -20,6 +20,7 @@ export * from "../assemble-video-input.js"
|
|
|
20
20
|
export * from "../seedance-2-inputs.js"
|
|
21
21
|
export * from "../gemini-omni-inputs.js"
|
|
22
22
|
export * from "../veo-i2v-inputs.js"
|
|
23
|
+
export * from "../frame-reference-slots.js"
|
|
23
24
|
export * from "../surround-fill.js"
|
|
24
25
|
// --- Start/end frame delivery: one plan for the dispatch and the editor ---
|
|
25
26
|
export * from "../frame-delivery.js"
|
|
@@ -16,6 +16,8 @@ export * from "../described-references.js"
|
|
|
16
16
|
export * from "../prompt-builder.js"
|
|
17
17
|
export * from "../prompt-builder-structured-fields.js"
|
|
18
18
|
export * from "../direction-registry.js"
|
|
19
|
+
export * from "../user-direction-refs.js"
|
|
20
|
+
export * from "../color-names.js"
|
|
19
21
|
export * from "../subject-registry.js"
|
|
20
22
|
export * from "../prompt-style-section.js"
|
|
21
23
|
export * from "../read-node-direction.js"
|
|
@@ -10,12 +10,17 @@ const REALISM_MODEL = { provider: "gpt-image-2", resolution: "2K" } as const
|
|
|
10
10
|
const SKIN_STUDY_MODEL = { provider: "seedream-5-pro", quality: "high" } as const
|
|
11
11
|
|
|
12
12
|
export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
13
|
+
// `minReferences` marks a preset that works from connected photos its prompt
|
|
14
|
+
// describes in words ("the attached photo"); `{image:N}` tokens count on their
|
|
15
|
+
// own, so a tokenized preset declares nothing. The groups stay all-or-nothing
|
|
16
|
+
// (factory-preset-references.test.ts), so a new preset copies the line along.
|
|
13
17
|
// ── Reference Sheet ──────────────────────────────────────────────────────
|
|
14
18
|
{
|
|
15
19
|
id: "generate-image/character-board",
|
|
16
20
|
name: "Character Board",
|
|
17
21
|
description: "Connect a sharp, well-lit photo → dense character sheet; reuse it as a reference for consistent shots.",
|
|
18
22
|
group: "Reference Sheet",
|
|
23
|
+
minReferences: 1,
|
|
19
24
|
data: {
|
|
20
25
|
provider: "gpt-image-2",
|
|
21
26
|
aspectRatio: "16:9",
|
|
@@ -31,6 +36,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
31
36
|
name: "Pose Board",
|
|
32
37
|
description: "Connect a sharp photo → animation-ready pose & expression sheet; reuse it as a reference for consistent action.",
|
|
33
38
|
group: "Reference Sheet",
|
|
39
|
+
minReferences: 1,
|
|
34
40
|
data: {
|
|
35
41
|
provider: "gpt-image-2",
|
|
36
42
|
aspectRatio: "16:9",
|
|
@@ -46,6 +52,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
46
52
|
name: "Location Board",
|
|
47
53
|
description: "Connect a photo → dense location sheet; reuse it as a reference for consistent scenes.",
|
|
48
54
|
group: "Reference Sheet",
|
|
55
|
+
minReferences: 1,
|
|
49
56
|
data: {
|
|
50
57
|
provider: "gpt-image-2",
|
|
51
58
|
aspectRatio: "16:9",
|
|
@@ -61,6 +68,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
61
68
|
name: "Product Board",
|
|
62
69
|
description: "Connect a photo → dense product sheet; reuse it as a reference for consistent shots.",
|
|
63
70
|
group: "Reference Sheet",
|
|
71
|
+
minReferences: 1,
|
|
64
72
|
data: {
|
|
65
73
|
provider: "gpt-image-2",
|
|
66
74
|
aspectRatio: "16:9",
|
|
@@ -76,6 +84,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
76
84
|
name: "Outfit Board",
|
|
77
85
|
description: "Connect a photo → dense wardrobe sheet; reuse it as a reference for consistent looks.",
|
|
78
86
|
group: "Reference Sheet",
|
|
87
|
+
minReferences: 1,
|
|
79
88
|
data: {
|
|
80
89
|
provider: "gpt-image-2",
|
|
81
90
|
aspectRatio: "16:9",
|
|
@@ -91,6 +100,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
91
100
|
name: "Scene Board",
|
|
92
101
|
description: "Connect a photo → set-dressing & props study; reuse it as a reference for consistent sets.",
|
|
93
102
|
group: "Reference Sheet",
|
|
103
|
+
minReferences: 1,
|
|
94
104
|
data: {
|
|
95
105
|
provider: "gpt-image-2",
|
|
96
106
|
aspectRatio: "16:9",
|
|
@@ -106,6 +116,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
106
116
|
name: "Creature Board",
|
|
107
117
|
description: "Connect a sharp photo → dense creature sheet; reuse it as a reference for consistent shots.",
|
|
108
118
|
group: "Reference Sheet",
|
|
119
|
+
minReferences: 1,
|
|
109
120
|
data: {
|
|
110
121
|
provider: "gpt-image-2",
|
|
111
122
|
aspectRatio: "16:9",
|
|
@@ -121,6 +132,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
121
132
|
name: "Vehicle Board",
|
|
122
133
|
description: "Connect a photo → dense vehicle sheet; reuse it as a reference for consistent shots.",
|
|
123
134
|
group: "Reference Sheet",
|
|
135
|
+
minReferences: 1,
|
|
124
136
|
data: {
|
|
125
137
|
provider: "gpt-image-2",
|
|
126
138
|
aspectRatio: "16:9",
|
|
@@ -136,6 +148,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
136
148
|
name: "Food Board",
|
|
137
149
|
description: "Connect a photo → dense dish sheet; reuse it as a reference for consistent food shots.",
|
|
138
150
|
group: "Reference Sheet",
|
|
151
|
+
minReferences: 1,
|
|
139
152
|
data: {
|
|
140
153
|
provider: "gpt-image-2",
|
|
141
154
|
aspectRatio: "16:9",
|
|
@@ -151,6 +164,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
151
164
|
name: "Mascot Board",
|
|
152
165
|
description: "Connect a mascot image → brand-character sheet with brand applications; reuse it for a consistent mascot.",
|
|
153
166
|
group: "Reference Sheet",
|
|
167
|
+
minReferences: 1,
|
|
154
168
|
data: {
|
|
155
169
|
provider: "gpt-image-2",
|
|
156
170
|
aspectRatio: "16:9",
|
|
@@ -166,6 +180,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
166
180
|
name: "Pet Board",
|
|
167
181
|
description: "Connect a sharp photo → dense pet sheet; reuse it as a reference for consistent shots.",
|
|
168
182
|
group: "Reference Sheet",
|
|
183
|
+
minReferences: 1,
|
|
169
184
|
data: {
|
|
170
185
|
provider: "gpt-image-2",
|
|
171
186
|
aspectRatio: "16:9",
|
|
@@ -181,6 +196,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
181
196
|
name: "Skin Detail Sheet",
|
|
182
197
|
description: "Connect a sharp face photo → a six-panel skin reference.",
|
|
183
198
|
group: "Reference Sheet",
|
|
199
|
+
minReferences: 1,
|
|
184
200
|
data: {
|
|
185
201
|
...SKIN_STUDY_MODEL,
|
|
186
202
|
aspectRatio: "3:2",
|
|
@@ -203,6 +219,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
203
219
|
name: "Character Reference Grid",
|
|
204
220
|
description: "Connect a photo → clean 4-angle identity grid (no decorations) — the strongest consistency anchor.",
|
|
205
221
|
group: "Cast & Consistency",
|
|
222
|
+
minReferences: 1,
|
|
206
223
|
data: {
|
|
207
224
|
provider: "gpt-image-2",
|
|
208
225
|
aspectRatio: "3:4",
|
|
@@ -218,6 +235,8 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
218
235
|
name: "Cast Mega Grid",
|
|
219
236
|
description: "Connect 2–4 character refs → one labeled cast sheet; then reference cast members by name in scenes.",
|
|
220
237
|
group: "Cast & Consistency",
|
|
238
|
+
// A cast sheet is two characters or more ("Connect 2–4 character refs").
|
|
239
|
+
minReferences: 2,
|
|
221
240
|
data: {
|
|
222
241
|
provider: "gpt-image-2",
|
|
223
242
|
aspectRatio: "3:4",
|
|
@@ -232,6 +251,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
232
251
|
name: "Cast Scene (by name)",
|
|
233
252
|
description: "Connect a cast grid → stage a scene naming the characters; never re-describe their looks.",
|
|
234
253
|
group: "Cast & Consistency",
|
|
254
|
+
minReferences: 1,
|
|
235
255
|
data: {
|
|
236
256
|
provider: "gpt-image-2",
|
|
237
257
|
aspectRatio: "16:9",
|
|
@@ -247,6 +267,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
247
267
|
name: "Label Elements",
|
|
248
268
|
description: "Step 1 · connect a photo → the same image with a numbered label on each changeable element.",
|
|
249
269
|
group: "Edit by Name",
|
|
270
|
+
minReferences: 1,
|
|
250
271
|
data: {
|
|
251
272
|
provider: "gpt-image-2",
|
|
252
273
|
quality: "high",
|
|
@@ -260,6 +281,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
260
281
|
name: "Apply Named Edit",
|
|
261
282
|
description: "Step 2 · connect the labeled sheet → type your change in the prompt (e.g. change \"drink 1\" to blue); the labels are removed from the result.",
|
|
262
283
|
group: "Edit by Name",
|
|
284
|
+
minReferences: 1,
|
|
263
285
|
data: {
|
|
264
286
|
provider: "gpt-image-2",
|
|
265
287
|
quality: "high",
|
|
@@ -1628,6 +1650,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
1628
1650
|
name: "Five Ages (Timeless Soul)",
|
|
1629
1651
|
description: "One person at 5 ages, studio line-up. Needs a reference photo.",
|
|
1630
1652
|
group: "Portrait Transformations",
|
|
1653
|
+
minReferences: 1,
|
|
1631
1654
|
data: {
|
|
1632
1655
|
provider: "gpt-image-2",
|
|
1633
1656
|
aspectRatio: "3:4",
|
|
@@ -1642,6 +1665,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
1642
1665
|
name: "Three-Age Triptych",
|
|
1643
1666
|
description: "Same person at 3 ages (~10 / 40 / 75). Needs a reference photo.",
|
|
1644
1667
|
group: "Portrait Transformations",
|
|
1668
|
+
minReferences: 1,
|
|
1645
1669
|
data: {
|
|
1646
1670
|
provider: "gpt-image-2",
|
|
1647
1671
|
aspectRatio: "3:4",
|
|
@@ -1656,6 +1680,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
1656
1680
|
name: "Five Ages — B&W",
|
|
1657
1681
|
description: "Monochrome age line-up. Needs a reference photo.",
|
|
1658
1682
|
group: "Portrait Transformations",
|
|
1683
|
+
minReferences: 1,
|
|
1659
1684
|
data: {
|
|
1660
1685
|
provider: "gpt-image-2",
|
|
1661
1686
|
aspectRatio: "3:4",
|
|
@@ -1670,6 +1695,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
1670
1695
|
name: "Decade Timeline",
|
|
1671
1696
|
description: "Same person across the '80s–2020s. Needs a reference photo.",
|
|
1672
1697
|
group: "Portrait Transformations",
|
|
1698
|
+
minReferences: 1,
|
|
1673
1699
|
data: {
|
|
1674
1700
|
provider: "gpt-image-2",
|
|
1675
1701
|
aspectRatio: "3:4",
|
|
@@ -1684,6 +1710,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
1684
1710
|
name: "Four Seasons",
|
|
1685
1711
|
description: "Same person in spring / summer / autumn / winter. Needs a reference photo.",
|
|
1686
1712
|
group: "Portrait Transformations",
|
|
1713
|
+
minReferences: 1,
|
|
1687
1714
|
data: {
|
|
1688
1715
|
provider: "gpt-image-2",
|
|
1689
1716
|
aspectRatio: "3:4",
|
|
@@ -1698,6 +1725,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
1698
1725
|
name: "Times of Day",
|
|
1699
1726
|
description: "Same person at dawn / midday / golden hour / night. Needs a reference photo.",
|
|
1700
1727
|
group: "Portrait Transformations",
|
|
1728
|
+
minReferences: 1,
|
|
1701
1729
|
data: {
|
|
1702
1730
|
provider: "gpt-image-2",
|
|
1703
1731
|
aspectRatio: "3:4",
|
|
@@ -1712,6 +1740,7 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
1712
1740
|
name: "Sports Jersey Portrait",
|
|
1713
1741
|
description: "You in an oversized team jersey, studio editorial. Needs a reference photo.",
|
|
1714
1742
|
group: "Portrait Transformations",
|
|
1743
|
+
minReferences: 1,
|
|
1715
1744
|
data: {
|
|
1716
1745
|
provider: "nano-banana-2-1",
|
|
1717
1746
|
aspectRatio: "4:5",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { T2I_TO_I2I_VARIANT } from "@nodaro/shared/models"
|
|
2
|
+
import { ownEntry } from "../own-entry.js"
|
|
2
3
|
import type { FactoryPreset } from "./types.js"
|
|
3
4
|
import { stylizedSubjectFor, editsFor } from "./shared-image.js"
|
|
4
5
|
import { GENERATE_IMAGE_PRESETS } from "./generate-image.js"
|
|
@@ -14,6 +15,8 @@ import { LOTTIE_OVERLAY_PRESETS } from "./lottie-overlay.js"
|
|
|
14
15
|
|
|
15
16
|
export type { FactoryPreset, FactoryPresetGroup } from "./types.js"
|
|
16
17
|
export { groupFactoryPresets } from "./types.js"
|
|
18
|
+
export { MAX_PRESET_REFERENCES, presetMinReferences } from "./references.js"
|
|
19
|
+
export { FACTORY_PRESET_PREVIEW_FILES, PRESET_PREVIEW_PATH, factoryPresetPreviewUrl } from "./previews.js"
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
22
|
* A shared preset as Modify Image takes it. Modify Image always works from a source photo, so its
|
|
@@ -55,6 +58,8 @@ export const FACTORY_PRESETS: Readonly<Record<string, readonly FactoryPreset[]>>
|
|
|
55
58
|
"lottie-overlay": LOTTIE_OVERLAY_PRESETS,
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
/** The catalog for `nodeType`, or an empty one — also for a name that is an
|
|
62
|
+
* Object.prototype member (`constructor`), which is no catalog of ours. */
|
|
58
63
|
export function getFactoryPresets(nodeType: string): readonly FactoryPreset[] {
|
|
59
|
-
return FACTORY_PRESETS
|
|
64
|
+
return ownEntry(FACTORY_PRESETS, nodeType) ?? []
|
|
60
65
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { FACTORY_PRESET_PREVIEW_FILES } from "./preview-files.generated.js"
|
|
2
|
+
|
|
3
|
+
export { FACTORY_PRESET_PREVIEW_FILES }
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Where the factory preset previews live, relative to the app's origin:
|
|
7
|
+
* `frontend/public/preset-previews/` is served at `/preset-previews/` in every
|
|
8
|
+
* edition. Each name carries its content hash (`<slug>.<hash8>.webp`), so the
|
|
9
|
+
* server caches them as immutable and a regenerated picture is a new URL.
|
|
10
|
+
*/
|
|
11
|
+
export const PRESET_PREVIEW_PATH = "/preset-previews/"
|
|
12
|
+
|
|
13
|
+
function originOf(baseUrl: string): string | undefined {
|
|
14
|
+
try {
|
|
15
|
+
const url = new URL(baseUrl)
|
|
16
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return undefined
|
|
17
|
+
return url.origin + url.pathname.replace(/\/+$/, "")
|
|
18
|
+
} catch {
|
|
19
|
+
return undefined
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The absolute URL of a factory preset's example picture on the installation
|
|
25
|
+
* at `baseUrl` (its public origin, e.g. "https://app.nodaro.ai"), or null when
|
|
26
|
+
* the preset has none. Only a preset whose file is in the committed map gets a
|
|
27
|
+
* URL, so it never points at a file that does not exist.
|
|
28
|
+
*
|
|
29
|
+
* @param files the preset id → file map; defaults to the committed one
|
|
30
|
+
*/
|
|
31
|
+
export function factoryPresetPreviewUrl(
|
|
32
|
+
presetId: string,
|
|
33
|
+
baseUrl: string,
|
|
34
|
+
files: Readonly<Record<string, string>> = FACTORY_PRESET_PREVIEW_FILES,
|
|
35
|
+
): string | null {
|
|
36
|
+
const file = Object.prototype.hasOwnProperty.call(files, presetId) ? files[presetId] : undefined
|
|
37
|
+
if (!file) return null
|
|
38
|
+
const origin = originOf(baseUrl)
|
|
39
|
+
return origin ? `${origin}${PRESET_PREVIEW_PATH}${file}` : null
|
|
40
|
+
}
|