@nodaro/prompts 1.46.0 → 1.48.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/{catalog-packs-C9CXEWfl.d.cts → catalog-packs-BDsrv8St.d.cts} +1 -1
- package/dist/{catalog-packs-D7NJpo4k.d.ts → catalog-packs-CAK42voB.d.ts} +1 -1
- package/dist/{chunk-SMKIVT7P.js → chunk-36XMRVY4.js} +4 -4
- package/dist/{chunk-SMKIVT7P.js.map → chunk-36XMRVY4.js.map} +1 -1
- package/dist/{chunk-JHEZJKTX.cjs → chunk-54BYDWAM.cjs} +62 -52
- package/dist/chunk-54BYDWAM.cjs.map +1 -0
- package/dist/{chunk-IKHRZ5UQ.js → chunk-BTV2I7EN.js} +3 -3
- package/dist/{chunk-IKHRZ5UQ.js.map → chunk-BTV2I7EN.js.map} +1 -1
- package/dist/{chunk-57PTYJJX.js → chunk-CMEUYFBZ.js} +13 -13
- package/dist/chunk-CMEUYFBZ.js.map +1 -0
- package/dist/{chunk-2OIKQ4WB.js → chunk-GZVCBSFG.js} +5 -5
- package/dist/{chunk-2OIKQ4WB.js.map → chunk-GZVCBSFG.js.map} +1 -1
- package/dist/{chunk-FCTK7RGO.cjs → chunk-IMQB7GIK.cjs} +42 -39
- package/dist/chunk-IMQB7GIK.cjs.map +1 -0
- package/dist/{chunk-2HJJBXGA.js → chunk-JHRVNWBS.js} +6 -6
- package/dist/{chunk-2HJJBXGA.js.map → chunk-JHRVNWBS.js.map} +1 -1
- package/dist/{chunk-OSKABA64.cjs → chunk-JV2WSMAA.cjs} +8 -8
- package/dist/{chunk-OSKABA64.cjs.map → chunk-JV2WSMAA.cjs.map} +1 -1
- package/dist/{chunk-VAK4RUNU.cjs → chunk-KNAKOSKV.cjs} +13 -13
- package/dist/{chunk-VAK4RUNU.cjs.map → chunk-KNAKOSKV.cjs.map} +1 -1
- package/dist/{chunk-TXHFL725.js → chunk-LD47QOKS.js} +62 -53
- package/dist/chunk-LD47QOKS.js.map +1 -0
- package/dist/{chunk-QKD2KUZW.cjs → chunk-MGK6SFTV.cjs} +20 -6
- package/dist/chunk-MGK6SFTV.cjs.map +1 -0
- package/dist/{chunk-YCCQXX3U.cjs → chunk-MYVJVOPB.cjs} +39 -39
- package/dist/{chunk-YCCQXX3U.cjs.map → chunk-MYVJVOPB.cjs.map} +1 -1
- package/dist/{chunk-PHR4UETI.cjs → chunk-PJFX3MFI.cjs} +18 -18
- package/dist/{chunk-PHR4UETI.cjs.map → chunk-PJFX3MFI.cjs.map} +1 -1
- package/dist/{chunk-PUWJ2ZBG.js → chunk-RRHU6MD5.js} +20 -6
- package/dist/chunk-RRHU6MD5.js.map +1 -0
- package/dist/{chunk-NN4FL7SB.js → chunk-XBRW75LM.js} +4 -4
- package/dist/{chunk-NN4FL7SB.js.map → chunk-XBRW75LM.js.map} +1 -1
- package/dist/{chunk-C4FDHCEZ.cjs → chunk-XGNUOU4F.cjs} +125 -125
- package/dist/{chunk-C4FDHCEZ.cjs.map → chunk-XGNUOU4F.cjs.map} +1 -1
- package/dist/{chunk-CPKYIVWP.js → chunk-YXJSPQC6.js} +12 -9
- package/dist/chunk-YXJSPQC6.js.map +1 -0
- package/dist/{chunk-OY7XOPDG.cjs → chunk-Z755PH3K.cjs} +57 -57
- package/dist/chunk-Z755PH3K.cjs.map +1 -0
- package/dist/entries/assembly.cjs +28 -28
- package/dist/entries/assembly.d.cts +1 -2
- package/dist/entries/assembly.d.ts +1 -2
- package/dist/entries/assembly.js +5 -5
- package/dist/entries/catalog-packs.d.cts +3 -3
- package/dist/entries/catalog-packs.d.ts +3 -3
- package/dist/entries/person.d.cts +2 -2
- package/dist/entries/person.d.ts +2 -2
- package/dist/entries/picker-catalogs.cjs +56 -56
- package/dist/entries/picker-catalogs.d.cts +26 -4
- package/dist/entries/picker-catalogs.d.ts +26 -4
- package/dist/entries/picker-catalogs.js +4 -4
- package/dist/entries/pickers.cjs +377 -373
- package/dist/entries/pickers.d.cts +473 -2
- package/dist/entries/pickers.d.ts +473 -2
- package/dist/entries/pickers.js +2 -2
- package/dist/entries/presets.cjs +35 -35
- package/dist/entries/presets.js +1 -1
- package/dist/entries/prompt-builder.cjs +75 -75
- package/dist/entries/prompt-builder.d.cts +2 -3
- package/dist/entries/prompt-builder.d.ts +2 -3
- package/dist/entries/prompt-builder.js +4 -4
- package/dist/index.cjs +564 -560
- package/dist/index.d.cts +5 -6
- package/dist/index.d.ts +5 -6
- package/dist/index.js +9 -9
- package/dist/{picker-catalogs-C3utGMRB.d.cts → picker-catalogs-BiRCs_5G.d.cts} +8 -0
- package/dist/{picker-catalogs-BM8IphF1.d.ts → picker-catalogs-CX8pHi8q.d.ts} +8 -0
- package/dist/{subject-registry-DFiGx49v.d.ts → subject-registry-Cf802LQ5.d.ts} +8 -4
- package/dist/{subject-registry-CQi_CZfJ.d.cts → subject-registry-D2uMBVpP.d.cts} +8 -4
- package/package.json +2 -2
- package/src/__tests__/assemble-video-input.test.ts +13 -11
- package/src/__tests__/direction-registry.test.ts +5 -5
- package/src/__tests__/factory-presets.test.ts +26 -2
- package/src/__tests__/image-unwired-reference-tokens.test.ts +3 -2
- package/src/__tests__/prompt-style-section.test.ts +2 -2
- package/src/__tests__/style-video-wording.test.ts +258 -0
- package/src/catalog-overlay.ts +15 -1
- package/src/direction-registry.ts +25 -7
- package/src/factory-presets/generate-image.ts +18 -4
- package/src/parameter-prompt-hint.ts +23 -7
- package/src/picker-catalogs.ts +11 -0
- package/src/style.ts +78 -49
- package/dist/chunk-57PTYJJX.js.map +0 -1
- package/dist/chunk-CPKYIVWP.js.map +0 -1
- package/dist/chunk-FCTK7RGO.cjs.map +0 -1
- package/dist/chunk-JHEZJKTX.cjs.map +0 -1
- package/dist/chunk-OY7XOPDG.cjs.map +0 -1
- package/dist/chunk-PUWJ2ZBG.js.map +0 -1
- package/dist/chunk-QKD2KUZW.cjs.map +0 -1
- package/dist/chunk-TXHFL725.js.map +0 -1
- package/dist/transitions-BLFfAJUH.d.cts +0 -450
- package/dist/transitions-BgwWM37i.d.ts +0 -450
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
renderDirectionHints,
|
|
21
21
|
} from "../direction-registry.js"
|
|
22
22
|
import { joinPromptHints } from "../prompt-hint-join.js"
|
|
23
|
-
import { getStylePromptHint } from "../style.js"
|
|
23
|
+
import { getStylePromptHint, getStyleVideoPromptHint } from "../style.js"
|
|
24
24
|
import { getColorLookPromptHint } from "../color-look.js"
|
|
25
25
|
import { getEraPromptHint } from "../era.js"
|
|
26
26
|
import { getCameraFormatPromptHint } from "../camera-format.js"
|
|
@@ -98,7 +98,7 @@ describe("partitionStyleClauses — which slot a clause lands in", () => {
|
|
|
98
98
|
)
|
|
99
99
|
expect(slots.get(getCameraMotionTerm(CAMERA_MOTION))).toBe("body")
|
|
100
100
|
expect(slots.get(renderTransitionBases([TRANSITION])[0])).toBe("body")
|
|
101
|
-
expect(slots.get(
|
|
101
|
+
expect(slots.get(getStyleVideoPromptHint(STYLE))).toBe("film")
|
|
102
102
|
expect(slots.get(getFramingPromptHint(SHOT_SIZE))).toBe("scene")
|
|
103
103
|
})
|
|
104
104
|
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every style carries a VIDEO-only wording (`videoPromptHint`): what the medium
|
|
3
|
+
* is, then its concrete techniques (the pattern that styled every shot in video
|
|
4
|
+
* tests). Every video surface reads it through `getStyleVideoPromptHint`
|
|
5
|
+
* (falling back to `promptHint` for a pack style without one); every image
|
|
6
|
+
* surface keeps `promptHint`. These tests pin:
|
|
7
|
+
* - stills render exactly the image wording for every style;
|
|
8
|
+
* - video renders each style's own video wording, the three tested on video
|
|
9
|
+
* and the two chosen for 3D Render and Charcoal word for word, the
|
|
10
|
+
* photographic ones still photographic;
|
|
11
|
+
* - a deployment's catalog packs reach the video wording the same way they
|
|
12
|
+
* reach the image wording, and a rewrite of the image wording never ships
|
|
13
|
+
* beside the stock video wording.
|
|
14
|
+
*/
|
|
15
|
+
import { afterEach, describe, expect, it } from "vitest"
|
|
16
|
+
import {
|
|
17
|
+
STYLES,
|
|
18
|
+
getStyle,
|
|
19
|
+
getStylePromptHint,
|
|
20
|
+
getStyleVideoPromptHint,
|
|
21
|
+
getPickerCatalog,
|
|
22
|
+
projectPickerCatalog,
|
|
23
|
+
registerCatalogPack,
|
|
24
|
+
resetCatalogPacks,
|
|
25
|
+
renderDirectionHints,
|
|
26
|
+
composeVideoPromptText,
|
|
27
|
+
assembleImageInput,
|
|
28
|
+
getParameterPromptHint,
|
|
29
|
+
type PickerCatalogInput,
|
|
30
|
+
} from "../index.js"
|
|
31
|
+
|
|
32
|
+
afterEach(() => resetCatalogPacks())
|
|
33
|
+
|
|
34
|
+
const LOW_POLY_VIDEO =
|
|
35
|
+
"Stylised low poly 3D animation, computer-generated: every person, face, vehicle and room built from large flat-shaded triangles, visible facets on every surface, flat untextured colours, crisp shadow edges between facets."
|
|
36
|
+
/** The three wordings tested on video (Seedance 2.5), word for word. */
|
|
37
|
+
const TESTED_ON_VIDEO: Readonly<Record<string, string>> = {
|
|
38
|
+
"pop-art": "Flat 2D cel-shaded comic illustration, hand-drawn: thick uniform black ink outlines, flat colour fills, Ben-Day dot shading, bold saturated primary colours, high contrast.",
|
|
39
|
+
claymation: "Stop-motion claymation, handmade: every person, vehicle and set sculpted from matte plasticine clay with visible fingerprints and tool marks, rounded simplified forms, miniature handcrafted diorama sets, soft warm studio lighting.",
|
|
40
|
+
anime: "2D Japanese anime, hand-drawn cel animation: clean thin ink line art, flat cel shading with one hard shadow tone, large expressive eyes, hair drawn as solid shapes, saturated colours, painted backgrounds.",
|
|
41
|
+
}
|
|
42
|
+
/** The 3D Render and Charcoal video wordings as chosen, word for word, beside their unchanged image wordings. */
|
|
43
|
+
const CHOSEN_FOR_VIDEO: Readonly<Record<string, { readonly video: string; readonly image: string }>> = {
|
|
44
|
+
"3d-render": {
|
|
45
|
+
video: "Stylised 3D render animation, computer-generated: every person, vehicle and room modelled in smooth matte CG surfaces, simplified clean forms, soft studio lighting, gentle ambient occlusion, bright clean colours.",
|
|
46
|
+
image: "3D rendered style, modern CGI aesthetic with clean shading, subtle ambient occlusion and crisp materials",
|
|
47
|
+
},
|
|
48
|
+
charcoal: {
|
|
49
|
+
video: "Rough 2D charcoal sketch, hand-drawn on white paper: every person and every face drawn in heavy black charcoal outlines and loose hatched strokes, white paper showing through, smudged edges, bold gestural lines.",
|
|
50
|
+
image: "rendered as a charcoal drawing on toothy paper, soft black and grey tonal masses with smudged blended midtones, sharp accented darks, expressive gestural strokes and the raw immediacy of a fine-art figure study",
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
/** The photographic styles: their video wording keeps them photographed, never drawn. */
|
|
54
|
+
const PHOTOGRAPHIC = ["photorealistic", "cinematic", "noir", "retro-vintage", "early-color-photo"]
|
|
55
|
+
const videoOf = (id: string) => STYLES.find((s) => s.id === id)!.videoPromptHint!
|
|
56
|
+
const LOW_POLY_IMAGE =
|
|
57
|
+
"low poly style, faceted geometric 3D aesthetic with visible flat triangular faces, simplified forms and crisp shadow edges between facets"
|
|
58
|
+
|
|
59
|
+
const styleNode = (style: string) => ({ id: "s1", type: "style", data: { style } })
|
|
60
|
+
|
|
61
|
+
describe("the catalog", () => {
|
|
62
|
+
it("has 49 styles, and every one carries its own video wording, different from its image wording", () => {
|
|
63
|
+
expect(STYLES).toHaveLength(49)
|
|
64
|
+
for (const s of STYLES) {
|
|
65
|
+
expect(s.videoPromptHint, s.id).toEqual(expect.any(String))
|
|
66
|
+
expect(s.videoPromptHint, s.id).not.toBe(s.promptHint)
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it("each video wording names the medium, then its techniques after a colon: one sentence, one line", () => {
|
|
71
|
+
for (const s of STYLES) {
|
|
72
|
+
const v = s.videoPromptHint!
|
|
73
|
+
expect(v, s.id).toMatch(/^[^:]+: \S[^\n]*\.$/)
|
|
74
|
+
expect(v.length, s.id).toBeLessThanOrEqual(240)
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it("the three wordings tested on video are word for word", () => {
|
|
79
|
+
for (const [id, wording] of Object.entries(TESTED_ON_VIDEO)) expect(getStyleVideoPromptHint(id), id).toBe(wording)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it("the 3D Render and Charcoal video wordings are word for word, and their image wordings unchanged", () => {
|
|
83
|
+
for (const [id, { video, image }] of Object.entries(CHOSEN_FOR_VIDEO)) {
|
|
84
|
+
expect(getStyleVideoPromptHint(id), id).toBe(video)
|
|
85
|
+
expect(getStylePromptHint(id), id).toBe(image)
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it("the photographic styles stay photographed on video", () => {
|
|
90
|
+
for (const id of PHOTOGRAPHIC) expect(getStyleVideoPromptHint(id), id).toMatch(/live-action|photograph/i)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it("Low Poly keeps its image wording and carries the video wording", () => {
|
|
94
|
+
expect(getStylePromptHint("low-poly")).toBe(LOW_POLY_IMAGE)
|
|
95
|
+
expect(getStyleVideoPromptHint("low-poly")).toBe(LOW_POLY_VIDEO)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it("every style's video wording is its own, read through getStyleVideoPromptHint", () => {
|
|
99
|
+
for (const s of STYLES) expect(getStyleVideoPromptHint(s.id), s.id).toBe(s.videoPromptHint)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it("an unknown or empty id has no wording on either surface", () => {
|
|
103
|
+
expect(getStyleVideoPromptHint("no-such-style")).toBe("")
|
|
104
|
+
expect(getStyleVideoPromptHint(undefined)).toBe("")
|
|
105
|
+
expect(getStyleVideoPromptHint("")).toBe("")
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe("image surfaces read only promptHint, for all 49 styles", () => {
|
|
110
|
+
it("the direction fold on image", () => {
|
|
111
|
+
for (const s of STYLES) {
|
|
112
|
+
expect(renderDirectionHints({ style: s.id }, { surface: "image" }), s.id).toEqual([s.promptHint])
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it("the assembled image prompt", () => {
|
|
117
|
+
for (const s of STYLES) {
|
|
118
|
+
const { prompt } = assembleImageInput({ userPrompt: "a man walks", provider: "flux-2-max", direction: { style: s.id } })
|
|
119
|
+
expect(prompt, s.id).toBe(`a man walks\n\n[style]:\n${s.promptHint}`)
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it("a Style node's own hint (no surface, and surface image)", () => {
|
|
124
|
+
for (const s of STYLES) {
|
|
125
|
+
expect(getParameterPromptHint(styleNode(s.id)), s.id).toBe(s.promptHint)
|
|
126
|
+
expect(getParameterPromptHint(styleNode(s.id), undefined, { surface: "image" }), s.id).toBe(s.promptHint)
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
describe("video surfaces read the video wording", () => {
|
|
132
|
+
it("the direction fold on video: each style's video wording", () => {
|
|
133
|
+
for (const s of STYLES) expect(renderDirectionHints({ style: s.id }, { surface: "video" }), s.id).toEqual([videoOf(s.id)])
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it("the composed video prompt", () => {
|
|
137
|
+
for (const s of STYLES) {
|
|
138
|
+
expect(composeVideoPromptText("a man walks", { style: s.id }), s.id).toBe(`a man walks\n\n[style]:\n${videoOf(s.id)}`)
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it("a Style node folded into a video consumer", () => {
|
|
143
|
+
for (const s of STYLES) {
|
|
144
|
+
expect(getParameterPromptHint(styleNode(s.id), undefined, { surface: "video" }), s.id).toBe(videoOf(s.id))
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it("keeps the node's own pre/post text around the video wording", () => {
|
|
149
|
+
const node = { id: "s1", type: "style", data: { style: "low-poly", preText: "before", postText: "after" } }
|
|
150
|
+
// The video wording ends a sentence, so what follows it is joined with a space (joinHintFragments).
|
|
151
|
+
expect(getParameterPromptHint(node, undefined, { surface: "video" })).toBe(`before, ${LOW_POLY_VIDEO} after`)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it("compact mode injects the same term on both surfaces", () => {
|
|
155
|
+
expect(renderDirectionHints({ style: "low-poly" }, { surface: "video", mode: "compact" })).toEqual(["low poly"])
|
|
156
|
+
const node = { id: "s1", type: "style", data: { style: "low-poly", hintMode: "compact" } }
|
|
157
|
+
expect(getParameterPromptHint(node, undefined, { surface: "video" })).toBe("low poly")
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it("a Style node on a camera move's start state takes the video surface down with it", () => {
|
|
161
|
+
const nodes = [
|
|
162
|
+
{ id: "cm", type: "camera-motion", data: { cameraMotion: "dolly-in" } },
|
|
163
|
+
styleNode("low-poly"),
|
|
164
|
+
]
|
|
165
|
+
const edges = [{ id: "e1", source: "s1", target: "cm", targetHandle: "startState" }]
|
|
166
|
+
const video = getParameterPromptHint(nodes[0], { nodes, edges }, { surface: "video" })
|
|
167
|
+
const image = getParameterPromptHint(nodes[0], { nodes, edges })
|
|
168
|
+
expect(video).toContain(LOW_POLY_VIDEO)
|
|
169
|
+
expect(image).toContain(LOW_POLY_IMAGE)
|
|
170
|
+
expect(image).not.toContain(LOW_POLY_VIDEO)
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
describe("catalog packs", () => {
|
|
175
|
+
const styleCatalog = () => getPickerCatalog("style")!
|
|
176
|
+
const replaceWith = (mapOption: (o: NonNullable<PickerCatalogInput["options"]>[number]) => object) => {
|
|
177
|
+
const base = styleCatalog()
|
|
178
|
+
registerCatalogPack({
|
|
179
|
+
id: "t-style-replace",
|
|
180
|
+
catalogId: "style",
|
|
181
|
+
mode: "replace",
|
|
182
|
+
catalog: { ...base, options: base.options!.map((o) => ({ ...o, ...mapOption(o) })) },
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
it("the composed catalog carries every style's video wording", () => {
|
|
187
|
+
const options = styleCatalog().options!
|
|
188
|
+
expect(options).toHaveLength(49)
|
|
189
|
+
for (const o of options) expect(o.videoPromptHint, o.id).toBe(videoOf(o.id))
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it("the wire projection does not carry it", () => {
|
|
193
|
+
const projected = projectPickerCatalog(styleCatalog(), { detail: "full" })
|
|
194
|
+
expect(JSON.stringify(projected)).not.toContain("videoPromptHint")
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it("a deny pack: a denied style has no video wording either", () => {
|
|
198
|
+
registerCatalogPack({ id: "t-deny", catalogId: "style", mode: "deny", denyIds: ["low-poly"] })
|
|
199
|
+
expect(getStyleVideoPromptHint("low-poly")).toBe("")
|
|
200
|
+
expect(renderDirectionHints({ style: "low-poly" }, { surface: "video" })).toEqual([])
|
|
201
|
+
// A sibling the pack did not touch still resolves on both surfaces.
|
|
202
|
+
expect(getStyleVideoPromptHint("anime")).toBe(TESTED_ON_VIDEO.anime)
|
|
203
|
+
expect(getStylePromptHint("anime")).toBe(STYLES.find((s) => s.id === "anime")!.promptHint)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it("a deny pack on ANOTHER style keeps Low Poly's video wording", () => {
|
|
207
|
+
registerCatalogPack({ id: "t-deny", catalogId: "style", mode: "deny", denyIds: ["anime"] })
|
|
208
|
+
expect(getStyleVideoPromptHint("low-poly")).toBe(LOW_POLY_VIDEO)
|
|
209
|
+
expect(getStylePromptHint("low-poly")).toBe(LOW_POLY_IMAGE)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it("the browser's copy of the server catalog (full detail, no video wording) keeps the stock video wording", () => {
|
|
213
|
+
// What `catalog-bootstrap` registers on a curated deployment: the server's
|
|
214
|
+
// composed catalog read back from `/v1/catalogs?detail=full`.
|
|
215
|
+
const wire = projectPickerCatalog(styleCatalog(), { detail: "full" }) as unknown as PickerCatalogInput
|
|
216
|
+
registerCatalogPack({ id: "wire:style", catalogId: "style", mode: "replace", catalog: wire })
|
|
217
|
+
expect(getStyleVideoPromptHint("low-poly")).toBe(LOW_POLY_VIDEO)
|
|
218
|
+
expect(getStylePromptHint("low-poly")).toBe(LOW_POLY_IMAGE)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it("a pack that rewrites the image wording drops the stock video wording (video follows the rewrite)", () => {
|
|
222
|
+
replaceWith((o) => (o.id === "low-poly" ? { promptHint: "curated low poly wording", videoPromptHint: undefined } : {}))
|
|
223
|
+
expect(getStylePromptHint("low-poly")).toBe("curated low poly wording")
|
|
224
|
+
expect(getStyleVideoPromptHint("low-poly")).toBe("curated low poly wording")
|
|
225
|
+
expect(getStyle("low-poly")?.videoPromptHint).toBeUndefined()
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it("a pack may carry its own video wording", () => {
|
|
229
|
+
replaceWith((o) => (o.id === "low-poly" ? { promptHint: "curated still", videoPromptHint: "curated motion" } : {}))
|
|
230
|
+
expect(getStylePromptHint("low-poly")).toBe("curated still")
|
|
231
|
+
expect(getStyleVideoPromptHint("low-poly")).toBe("curated motion")
|
|
232
|
+
expect(renderDirectionHints({ style: "low-poly" }, { surface: "video" })).toEqual(["curated motion"])
|
|
233
|
+
expect(renderDirectionHints({ style: "low-poly" }, { surface: "image" })).toEqual(["curated still"])
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it("a pack may replace a stock style's video wording and keep its image wording", () => {
|
|
237
|
+
replaceWith((o) => (o.id === "anime" ? { videoPromptHint: "anime video wording" } : {}))
|
|
238
|
+
expect(getStylePromptHint("anime")).toBe(STYLES.find((s) => s.id === "anime")!.promptHint)
|
|
239
|
+
expect(getStyleVideoPromptHint("anime")).toBe("anime video wording")
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
it("a pack-ADDED style resolves its own video wording, else its image wording", () => {
|
|
243
|
+
registerCatalogPack({
|
|
244
|
+
id: "t-extend",
|
|
245
|
+
catalogId: "style",
|
|
246
|
+
mode: "extend",
|
|
247
|
+
options: [
|
|
248
|
+
{ id: "claymation-x", label: "Clay X", promptHint: "clay still", videoPromptHint: "clay motion" },
|
|
249
|
+
{ id: "inkwash-x", label: "Ink X", promptHint: "ink still" },
|
|
250
|
+
],
|
|
251
|
+
})
|
|
252
|
+
expect(getStyleVideoPromptHint("claymation-x")).toBe("clay motion")
|
|
253
|
+
expect(getStylePromptHint("claymation-x")).toBe("clay still")
|
|
254
|
+
expect(getStyleVideoPromptHint("inkwash-x")).toBe("ink still")
|
|
255
|
+
expect(getParameterPromptHint(styleNode("claymation-x"), undefined, { surface: "video" })).toBe("clay motion")
|
|
256
|
+
expect(getParameterPromptHint(styleNode("claymation-x"))).toBe("clay still")
|
|
257
|
+
})
|
|
258
|
+
})
|
package/src/catalog-overlay.ts
CHANGED
|
@@ -26,6 +26,16 @@ import { catalogPacksVersion, hasCatalogPacksFor } from "./catalog-packs.js"
|
|
|
26
26
|
* carry (typed extras a specific builder reads) survive from the base.
|
|
27
27
|
* - pack-ADDED id (no base entry) → the composed option itself.
|
|
28
28
|
*
|
|
29
|
+
* THE VIDEO WORDING (`videoPromptHint`, styles only) pairs with the image
|
|
30
|
+
* wording it was written beside, so it cannot simply survive from the base
|
|
31
|
+
* like a typed extra: a pack that rewrites a style's `promptHint` would then
|
|
32
|
+
* still inject the stock video text. The composed option's own
|
|
33
|
+
* `videoPromptHint` wins when it carries one; otherwise the base's is kept
|
|
34
|
+
* only while the composed `promptHint` is the stock one (a deny / extend pack,
|
|
35
|
+
* or the browser's `replace` copy of the server's composed catalog, which
|
|
36
|
+
* carries `promptHint` but not the video wording); a rewritten `promptHint`
|
|
37
|
+
* drops it, so video falls back to the pack's own wording.
|
|
38
|
+
*
|
|
29
39
|
* LATE-BOUND RESOLVER, ON PURPOSE. The catalog modules cannot import
|
|
30
40
|
* `getPickerCatalog`: picker-catalogs.ts imports every one of them to build
|
|
31
41
|
* `PICKER_CATALOGS` at module scope, and a module-scope cycle is a TDZ error
|
|
@@ -91,7 +101,7 @@ export function overlayEntry<T extends { readonly id: string }>(
|
|
|
91
101
|
const opt = composedOptionIndex(catalogId).get(id)
|
|
92
102
|
if (!opt) return undefined
|
|
93
103
|
if (!base) return opt as unknown as T
|
|
94
|
-
|
|
104
|
+
const out: Record<string, unknown> = {
|
|
95
105
|
...base,
|
|
96
106
|
label: opt.label,
|
|
97
107
|
promptHint: opt.promptHint,
|
|
@@ -99,6 +109,10 @@ export function overlayEntry<T extends { readonly id: string }>(
|
|
|
99
109
|
...(opt.description !== undefined ? { description: opt.description } : {}),
|
|
100
110
|
...(opt.category !== undefined ? { category: opt.category } : {}),
|
|
101
111
|
}
|
|
112
|
+
const baseFields = base as { readonly promptHint?: unknown; readonly videoPromptHint?: unknown }
|
|
113
|
+
if (opt.videoPromptHint !== undefined) out.videoPromptHint = opt.videoPromptHint
|
|
114
|
+
else if (baseFields.videoPromptHint !== undefined && opt.promptHint !== baseFields.promptHint) delete out.videoPromptHint
|
|
115
|
+
return out as unknown as T
|
|
102
116
|
}
|
|
103
117
|
|
|
104
118
|
/**
|
|
@@ -56,7 +56,7 @@ import { getExposurePromptHint, getExposureTerm } from "./exposure-settings.js"
|
|
|
56
56
|
import { getColorLookPromptHint, getColorLookTerm } from "./color-look.js"
|
|
57
57
|
import { buildAtmosphereHints } from "./atmosphere.js"
|
|
58
58
|
import { buildPostProcessHints } from "./post-process-effects.js"
|
|
59
|
-
import { getStylePromptHint, getStyleTerm } from "./style.js"
|
|
59
|
+
import { getStylePromptHint, getStyleTerm, getStyleVideoPromptHint } from "./style.js"
|
|
60
60
|
import { buildMoodHints } from "./mood.js"
|
|
61
61
|
import { buildAestheticHints } from "./aesthetic.js"
|
|
62
62
|
import { getPhotoGenrePromptHint, getPhotoGenreTerm } from "./photo-genre.js"
|
|
@@ -125,8 +125,13 @@ export interface DirectionFieldSpec {
|
|
|
125
125
|
* Render selected ids in this catalog's own doctrine: per-id for independent
|
|
126
126
|
* catalogs, ONE blended clause for Mood / Aesthetic / Photographer, the
|
|
127
127
|
* catalog's own multi builder where it has one. `[]` when nothing resolves.
|
|
128
|
+
*
|
|
129
|
+
* `surface` is the stage being folded. Nearly every row ignores it (one
|
|
130
|
+
* wording serves stills and video); a row whose catalog authors a video-only
|
|
131
|
+
* wording (`style`, via `getStyleVideoPromptHint`) reads it. Absent reads as
|
|
132
|
+
* `"image"`.
|
|
128
133
|
*/
|
|
129
|
-
readonly render: (ids: readonly string[], mode: PickerHintMode) => string[]
|
|
134
|
+
readonly render: (ids: readonly string[], mode: PickerHintMode, surface?: "image" | "video") => string[]
|
|
130
135
|
}
|
|
131
136
|
|
|
132
137
|
// ── Render adapters (module-private) ────────────────────────────────────────
|
|
@@ -163,6 +168,16 @@ const viaStringBuilder =
|
|
|
163
168
|
return s.length > 0 ? [s] : []
|
|
164
169
|
}
|
|
165
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Per-id emission whose FULL wording depends on the surface: the image getter
|
|
173
|
+
* on a still, the video getter on video (which falls back to the image wording
|
|
174
|
+
* for an id that authors none). The compact term is the same on both.
|
|
175
|
+
*/
|
|
176
|
+
const perIdBySurface =
|
|
177
|
+
(image: (id: string) => string, video: (id: string) => string, compact: (id: string) => string) =>
|
|
178
|
+
(ids: readonly string[], mode: PickerHintMode, surface?: "image" | "video"): string[] =>
|
|
179
|
+
perId(surface === "video" ? video : image, compact)(ids, mode)
|
|
180
|
+
|
|
166
181
|
/** Mood's builder takes a RECORD and returns a list — and blends multi picks. */
|
|
167
182
|
const viaMood = (ids: readonly string[], mode: PickerHintMode): string[] =>
|
|
168
183
|
buildMoodHints({ mood: ids.length === 1 ? ids[0] : [...ids] }, mode).filter(
|
|
@@ -242,7 +257,7 @@ export const DIRECTION_FIELDS = [
|
|
|
242
257
|
{ key: "postProcess", surface: "image", family: "look", maxPicks: 2, render: viaListBuilder(buildPostProcessHints) },
|
|
243
258
|
|
|
244
259
|
// Style.
|
|
245
|
-
{ key: "style", surface: "both", family: "look", styleGroup: "film", maxPicks: 1, userRef: "moodboard", render:
|
|
260
|
+
{ key: "style", surface: "both", family: "look", styleGroup: "film", maxPicks: 1, userRef: "moodboard", render: perIdBySurface(getStylePromptHint, getStyleVideoPromptHint, getStyleTerm) },
|
|
246
261
|
{ key: "mood", surface: "both", family: "look", maxPicks: 2, render: viaMood },
|
|
247
262
|
{ key: "aesthetic", surface: "both", family: "look", maxPicks: 2, render: viaStringBuilder(buildAestheticHints) },
|
|
248
263
|
{ key: "photoGenre", surface: "image", family: "look", maxPicks: 1, render: perId(getPhotoGenrePromptHint, getPhotoGenreTerm) },
|
|
@@ -260,7 +275,9 @@ export const DIRECTION_FIELDS = [
|
|
|
260
275
|
{ key: "temporalFreeze", surface: "video", family: "motion", maxPicks: 1, render: temporal },
|
|
261
276
|
{ key: "temporalDirection", surface: "video", family: "motion", maxPicks: 1, render: temporal },
|
|
262
277
|
{ key: "temporalShutter", surface: "video", family: "motion", maxPicks: 1, render: temporal },
|
|
263
|
-
|
|
278
|
+
// Wrapped: `renderTransitionBases`' third parameter is a row's option values,
|
|
279
|
+
// not the surface `render` is handed.
|
|
280
|
+
{ key: "transition", surface: "video", family: "motion", maxPicks: 2, render: (ids, mode) => renderTransitionBases(ids, mode) },
|
|
264
281
|
{ key: "loopSubject", surface: "video", family: "motion", maxPicks: 1, render: perId(getLoopSubjectPromptHint, getLoopSubjectTerm) },
|
|
265
282
|
|
|
266
283
|
// ── LEGACY BLOCK — see the table doc above. Placed LAST, in today's exact
|
|
@@ -408,14 +425,15 @@ function renderRow(
|
|
|
408
425
|
spec: DirectionFieldSpec,
|
|
409
426
|
ids: readonly string[],
|
|
410
427
|
mode: PickerHintMode,
|
|
428
|
+
surface: "image" | "video",
|
|
411
429
|
refs: ReadonlyMap<string, string> | undefined,
|
|
412
430
|
): string[] {
|
|
413
431
|
if (spec.userRef === undefined || !ids.some((id) => parseUserDirectionRef(id) !== null)) {
|
|
414
|
-
return spec.render(ids, mode)
|
|
432
|
+
return spec.render(ids, mode, surface)
|
|
415
433
|
}
|
|
416
434
|
return ids.flatMap((id) => {
|
|
417
435
|
const ref = parseUserDirectionRef(id)
|
|
418
|
-
if (ref === null) return spec.render([id], mode)
|
|
436
|
+
if (ref === null) return spec.render([id], mode, surface)
|
|
419
437
|
const clause = ref.kind === spec.userRef ? refs?.get(id) : undefined
|
|
420
438
|
return clause ? [clause] : []
|
|
421
439
|
})
|
|
@@ -457,7 +475,7 @@ export function renderDirectionHintClauses(
|
|
|
457
475
|
spec.maxPicks,
|
|
458
476
|
)
|
|
459
477
|
if (ids.length === 0) continue
|
|
460
|
-
for (const hint of renderRow(spec, ids, modeForFamily(mode, spec.family), opts.refs)) {
|
|
478
|
+
for (const hint of renderRow(spec, ids, modeForFamily(mode, spec.family), opts.surface, opts.refs)) {
|
|
461
479
|
if (hint.length > 0 && !seen.has(hint)) {
|
|
462
480
|
seen.add(hint)
|
|
463
481
|
out.push({
|
|
@@ -293,10 +293,10 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
293
293
|
},
|
|
294
294
|
},
|
|
295
295
|
// ── Face Privacy (connect the photo → {image:1}) ─────────────────────────
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
//
|
|
296
|
+
// aspectRatio "auto" so the output keeps the input photo's shape. NOTE:
|
|
297
|
+
// KIE's gpt-image-2 spec forces resolution 1K when aspect is "auto" (the
|
|
298
|
+
// config panel's fail-safe snaps any other value back), so the gpt-image-2
|
|
299
|
+
// presets pin 1K explicitly rather than shipping a self-contradicting 2K.
|
|
300
300
|
{
|
|
301
301
|
id: "generate-image/faceless-3d-head",
|
|
302
302
|
name: "Faceless · 3D Blank Head",
|
|
@@ -340,6 +340,20 @@ export const GENERATE_IMAGE_PRESETS: readonly FactoryPreset[] = [
|
|
|
340
340
|
"opaque faces, visible facial features, eyes, nose, mouth, changing the background, altering clothing, hair or body, restyling the scene, relighting, changing pose or framing, distorted anatomy, watermark, blurry",
|
|
341
341
|
},
|
|
342
342
|
},
|
|
343
|
+
// GPT Image 2.5 Flare has no auto x resolution restriction, so this one keeps
|
|
344
|
+
// the photo's shape AND renders 2K.
|
|
345
|
+
{
|
|
346
|
+
id: "generate-image/remove-people",
|
|
347
|
+
name: "Remove People",
|
|
348
|
+
description: "Remove every person from the photo; the background stays exactly the same.",
|
|
349
|
+
group: "Face Privacy",
|
|
350
|
+
data: {
|
|
351
|
+
provider: "gpt-image-2-5-flare",
|
|
352
|
+
aspectRatio: "auto",
|
|
353
|
+
resolution: "2K",
|
|
354
|
+
promptPrefix: "remove all people from {image:1}\nkeep background exactly the same",
|
|
355
|
+
},
|
|
356
|
+
},
|
|
343
357
|
// ── Photography & Cinematic ──────────────────────────────────────────────
|
|
344
358
|
{
|
|
345
359
|
id: "generate-image/cinematic-portrait",
|
|
@@ -26,7 +26,7 @@ import { getCameraFormatPromptHint, getCameraFormatTerm } from "./camera-format.
|
|
|
26
26
|
import { getColorLookPromptHint, getColorLookTerm } from "./color-look.js"
|
|
27
27
|
import { buildAtmosphereHints } from "./atmosphere.js"
|
|
28
28
|
import { buildActionFxHints } from "./action-fx.js"
|
|
29
|
-
import { getStylePromptHint, getStyleTerm } from "./style.js"
|
|
29
|
+
import { getStylePromptHint, getStyleTerm, getStyleVideoPromptHint } from "./style.js"
|
|
30
30
|
import { getSettingPromptHint, getSettingTerm } from "./setting.js"
|
|
31
31
|
import { getLoopSubjectPromptHint, getLoopSubjectTerm } from "./loop-subject.js"
|
|
32
32
|
import { buildPersonHints } from "./person.js"
|
|
@@ -179,12 +179,26 @@ const MOTION_INTENSITY_HINTS: Readonly<Record<string, { readonly full: string; r
|
|
|
179
179
|
* transition / camera-motion / character-fx timing and start-state/end-state
|
|
180
180
|
* clauses, multi-pick joining and multi-dimension composition are emitted the
|
|
181
181
|
* same way in both modes.
|
|
182
|
+
*
|
|
183
|
+
* SURFACE — `opts.surface: "video"` is for a caller folding the hint into ONE
|
|
184
|
+
* video consumer's prompt (the canvas's video nodes): a Style node then injects
|
|
185
|
+
* its video wording (`getStyleVideoPromptHint`). Absent (or `"image"`) is the
|
|
186
|
+
* output this function always returned — what a still, a `{Label}` reference
|
|
187
|
+
* and every preview of the node's own text read, since a node's own output
|
|
188
|
+
* text has no single consumer. Every other node type ignores it.
|
|
182
189
|
*/
|
|
183
190
|
export function getParameterPromptHint(
|
|
184
191
|
node: HintNodeLike | undefined,
|
|
185
192
|
ctx?: HintGraphContext,
|
|
193
|
+
opts?: ParameterHintOptions,
|
|
186
194
|
): string {
|
|
187
|
-
return resolveParameterHint(node, ctx)
|
|
195
|
+
return resolveParameterHint(node, ctx, undefined, opts?.surface === "video" ? "video" : "image")
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** How `getParameterPromptHint` resolves a node. */
|
|
199
|
+
export interface ParameterHintOptions {
|
|
200
|
+
/** The stage the hint is folded into. Absent = `"image"`. */
|
|
201
|
+
readonly surface?: "image" | "video"
|
|
188
202
|
}
|
|
189
203
|
|
|
190
204
|
/**
|
|
@@ -198,6 +212,7 @@ function resolveParameterHint(
|
|
|
198
212
|
node: HintNodeLike | undefined,
|
|
199
213
|
ctx?: HintGraphContext,
|
|
200
214
|
inherited?: PickerHintMode,
|
|
215
|
+
surface: "image" | "video" = "image",
|
|
201
216
|
): string {
|
|
202
217
|
if (!node?.type) return ""
|
|
203
218
|
const data = (node.data ?? {}) as Record<string, unknown>
|
|
@@ -216,7 +231,7 @@ function resolveParameterHint(
|
|
|
216
231
|
// themselves parameter nodes (framing/tone/etc.) that don't need graph
|
|
217
232
|
// context, and avoiding recursion keeps the walk cycle-safe. The mode
|
|
218
233
|
// rides down so the composed clause stays at one level of detail.
|
|
219
|
-
const hint = resolveParameterHint(src, undefined, mode)
|
|
234
|
+
const hint = resolveParameterHint(src, undefined, mode, surface)
|
|
220
235
|
if (!hint) continue
|
|
221
236
|
if (edge.targetHandle === "startState") startHints.push(hint)
|
|
222
237
|
else if (edge.targetHandle === "endState") endHints.push(hint)
|
|
@@ -247,7 +262,7 @@ function resolveParameterHint(
|
|
|
247
262
|
if (edge.target !== node.id) continue
|
|
248
263
|
const src = ctx.nodes.find((n) => n.id === edge.source)
|
|
249
264
|
if (!src) continue
|
|
250
|
-
const hint = resolveParameterHint(src, undefined, mode) // no ctx — cycle-safe
|
|
265
|
+
const hint = resolveParameterHint(src, undefined, mode, surface) // no ctx — cycle-safe
|
|
251
266
|
if (!hint) continue
|
|
252
267
|
if (edge.targetHandle === "startState") startHints.push(hint)
|
|
253
268
|
else if (edge.targetHandle === "endState") endHints.push(hint)
|
|
@@ -301,7 +316,7 @@ function resolveParameterHint(
|
|
|
301
316
|
)
|
|
302
317
|
}
|
|
303
318
|
|
|
304
|
-
const base = resolveBaseHint(node.type, data, mode)
|
|
319
|
+
const base = resolveBaseHint(node.type, data, mode, surface)
|
|
305
320
|
if (base) return base
|
|
306
321
|
// Pack-extend fallback: a single-dim pack entry the per-catalog getter (which
|
|
307
322
|
// reads the frozen base array) can't resolve. Resolve it against the
|
|
@@ -313,7 +328,7 @@ function resolveParameterHint(
|
|
|
313
328
|
if (cat?.kind === "single" && cat.valueField) {
|
|
314
329
|
const id = typeof data[cat.valueField] === "string" ? (data[cat.valueField] as string) : ""
|
|
315
330
|
const opt = cat.options?.find((o) => o.id === id)
|
|
316
|
-
if (opt) return byMode(mode, opt.promptHint, resolveTerm(opt))
|
|
331
|
+
if (opt) return byMode(mode, surface === "video" ? (opt.videoPromptHint ?? opt.promptHint) : opt.promptHint, resolveTerm(opt))
|
|
317
332
|
}
|
|
318
333
|
return base
|
|
319
334
|
}
|
|
@@ -333,6 +348,7 @@ function resolveBaseHint(
|
|
|
333
348
|
type: string,
|
|
334
349
|
data: Record<string, unknown>,
|
|
335
350
|
mode: PickerHintMode = "full",
|
|
351
|
+
surface: "image" | "video" = "image",
|
|
336
352
|
): string {
|
|
337
353
|
switch (type) {
|
|
338
354
|
case "framing":
|
|
@@ -373,7 +389,7 @@ function resolveBaseHint(
|
|
|
373
389
|
case "action-fx":
|
|
374
390
|
return withCustomText(data, buildActionFxHints(data.actionFx, mode).join(", "))
|
|
375
391
|
case "style":
|
|
376
|
-
return withCustomText(data, byMode(mode, getStylePromptHint, getStyleTerm)(asStr(data.style)))
|
|
392
|
+
return withCustomText(data, byMode(mode, surface === "video" ? getStyleVideoPromptHint : getStylePromptHint, getStyleTerm)(asStr(data.style)))
|
|
377
393
|
case "setting":
|
|
378
394
|
return withCustomText(data, byMode(mode, getSettingPromptHint, getSettingTerm)(asStr(data.setting)))
|
|
379
395
|
case "loop-subject":
|
package/src/picker-catalogs.ts
CHANGED
|
@@ -132,6 +132,14 @@ export interface PickerOption {
|
|
|
132
132
|
* `label` and inject `term`; they never derive it themselves.
|
|
133
133
|
*/
|
|
134
134
|
readonly term: string
|
|
135
|
+
/**
|
|
136
|
+
* Style only: the wording a VIDEO prompt injects in place of `promptHint`
|
|
137
|
+
* (see `Style.videoPromptHint`). Present only on an entry that authors one;
|
|
138
|
+
* absent means video uses `promptHint`. A pack option may carry its own. On
|
|
139
|
+
* this in-memory catalog only: the wire projection (`projectPickerCatalog`)
|
|
140
|
+
* does not carry it.
|
|
141
|
+
*/
|
|
142
|
+
readonly videoPromptHint?: string
|
|
135
143
|
/**
|
|
136
144
|
* Transitions only: present (`true`) on a row whose mechanism is a CUT, so
|
|
137
145
|
* it takes no duration and no intensity — see `Transition.instant` /
|
|
@@ -221,6 +229,8 @@ interface BaseCatalogEntry {
|
|
|
221
229
|
readonly promptHint: string
|
|
222
230
|
/** Optional authored compact term (see `term.ts` for the convention). */
|
|
223
231
|
readonly term?: string
|
|
232
|
+
/** Style: see `Style.videoPromptHint`. Propagated verbatim into the flattened option. */
|
|
233
|
+
readonly videoPromptHint?: string
|
|
224
234
|
/** W1-a: see Person.adultOnly. Propagated verbatim into the flattened option. */
|
|
225
235
|
readonly adultOnly?: true
|
|
226
236
|
/** Transitions: see `Transition.instant`. Propagated into the flattened option as `instant: true`. */
|
|
@@ -267,6 +277,7 @@ function toOptions<T extends BaseCatalogEntry>(
|
|
|
267
277
|
term: resolveTerm(e),
|
|
268
278
|
}
|
|
269
279
|
if (e.description) opt.description = e.description
|
|
280
|
+
if (e.videoPromptHint) opt.videoPromptHint = e.videoPromptHint
|
|
270
281
|
if (categoryField) opt.category = e[categoryField] as unknown as string
|
|
271
282
|
if (e.adultOnly) opt.adultOnly = true
|
|
272
283
|
if (e.instant) opt.instant = true
|