@nodaro/prompts 1.53.2 → 1.55.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-DeMcSVCR.d.cts → catalog-packs-Bw7OpufR.d.cts} +1 -1
- package/dist/{catalog-packs-DjqvicAR.d.ts → catalog-packs-CyS8NdLM.d.ts} +1 -1
- package/dist/{chunk-HOZPYHRA.js → chunk-242NZ6SI.js} +5 -5
- package/dist/{chunk-HOZPYHRA.js.map → chunk-242NZ6SI.js.map} +1 -1
- package/dist/{chunk-GRSKB3P7.cjs → chunk-2LWYFWZL.cjs} +528 -2
- package/dist/chunk-2LWYFWZL.cjs.map +1 -0
- package/dist/{chunk-P4BXE4OA.cjs → chunk-2MTFPGQC.cjs} +13 -13
- package/dist/{chunk-P4BXE4OA.cjs.map → chunk-2MTFPGQC.cjs.map} +1 -1
- package/dist/{chunk-EKRO42T3.cjs → chunk-32BMZ4Q5.cjs} +113 -41
- package/dist/chunk-32BMZ4Q5.cjs.map +1 -0
- package/dist/{chunk-WQ7FWGZ2.js → chunk-4MCK7Y6C.js} +524 -3
- package/dist/chunk-4MCK7Y6C.js.map +1 -0
- package/dist/{chunk-FFPFBNXH.cjs → chunk-7ZWWJY24.cjs} +8 -2
- package/dist/chunk-7ZWWJY24.cjs.map +1 -0
- package/dist/{chunk-F22GZF7T.cjs → chunk-AMUXCE7X.cjs} +8 -8
- package/dist/{chunk-F22GZF7T.cjs.map → chunk-AMUXCE7X.cjs.map} +1 -1
- package/dist/{chunk-OQ3J7OGR.cjs → chunk-BHBZN2AP.cjs} +49 -49
- package/dist/{chunk-OQ3J7OGR.cjs.map → chunk-BHBZN2AP.cjs.map} +1 -1
- package/dist/{chunk-YAIPYPMM.js → chunk-BJGD4DBP.js} +4 -4
- package/dist/{chunk-YAIPYPMM.js.map → chunk-BJGD4DBP.js.map} +1 -1
- package/dist/{chunk-ZRORO6OM.js → chunk-CYYHQNW3.js} +4 -4
- package/dist/{chunk-ZRORO6OM.js.map → chunk-CYYHQNW3.js.map} +1 -1
- package/dist/{chunk-E5EKWWOO.cjs → chunk-FEHXLXAV.cjs} +8 -7
- package/dist/chunk-FEHXLXAV.cjs.map +1 -0
- package/dist/{chunk-5XPELUES.js → chunk-GJX5N63V.js} +8 -2
- package/dist/chunk-GJX5N63V.js.map +1 -0
- package/dist/{chunk-XR7RAHZI.js → chunk-HZH6XXYK.js} +81 -9
- package/dist/chunk-HZH6XXYK.js.map +1 -0
- package/dist/{chunk-OMRFKG4M.js → chunk-KBWUO4P7.js} +3 -3
- package/dist/{chunk-OMRFKG4M.js.map → chunk-KBWUO4P7.js.map} +1 -1
- package/dist/{chunk-XRCK3BCF.cjs → chunk-M25N26II.cjs} +55 -10
- package/dist/chunk-M25N26II.cjs.map +1 -0
- package/dist/{chunk-PHCYQWJP.js → chunk-NCF5TAR6.js} +55 -10
- package/dist/chunk-NCF5TAR6.js.map +1 -0
- package/dist/{chunk-NUC5RV4P.cjs → chunk-NDKJW7RP.cjs} +4 -4
- package/dist/{chunk-NUC5RV4P.cjs.map → chunk-NDKJW7RP.cjs.map} +1 -1
- package/dist/{chunk-ED47NNIN.js → chunk-OXNJVQ2V.js} +3 -3
- package/dist/{chunk-ED47NNIN.js.map → chunk-OXNJVQ2V.js.map} +1 -1
- package/dist/{chunk-PWYASBX2.cjs → chunk-P5HAPDV3.cjs} +18 -18
- package/dist/{chunk-PWYASBX2.cjs.map → chunk-P5HAPDV3.cjs.map} +1 -1
- package/dist/{chunk-EUU5W7XR.js → chunk-TMYYALRF.js} +4 -4
- package/dist/{chunk-EUU5W7XR.js.map → chunk-TMYYALRF.js.map} +1 -1
- package/dist/{chunk-H322GC7M.js → chunk-VX3BIUGY.js} +5 -4
- package/dist/chunk-VX3BIUGY.js.map +1 -0
- package/dist/{chunk-VBCVQPQ4.js → chunk-WDKQQ7WL.js} +3 -3
- package/dist/{chunk-VBCVQPQ4.js.map → chunk-WDKQQ7WL.js.map} +1 -1
- package/dist/{chunk-KTNCB2IE.cjs → chunk-XMRDZRGV.cjs} +35 -35
- package/dist/{chunk-KTNCB2IE.cjs.map → chunk-XMRDZRGV.cjs.map} +1 -1
- package/dist/{chunk-7BWDATUZ.cjs → chunk-Y3TSRO3S.cjs} +125 -125
- package/dist/{chunk-7BWDATUZ.cjs.map → chunk-Y3TSRO3S.cjs.map} +1 -1
- package/dist/entries/assembly.cjs +29 -29
- package/dist/entries/assembly.d.cts +9 -0
- package/dist/entries/assembly.d.ts +9 -0
- package/dist/entries/assembly.js +6 -6
- package/dist/entries/catalog-packs.d.cts +4 -4
- package/dist/entries/catalog-packs.d.ts +4 -4
- package/dist/entries/doctrine.cjs +46 -46
- package/dist/entries/doctrine.js +1 -1
- package/dist/entries/person.d.cts +3 -3
- package/dist/entries/person.d.ts +3 -3
- package/dist/entries/picker-art.cjs +22 -22
- package/dist/entries/picker-art.d.cts +1 -1
- package/dist/entries/picker-art.d.ts +1 -1
- package/dist/entries/picker-art.js +2 -2
- package/dist/entries/picker-catalogs.cjs +57 -57
- package/dist/entries/picker-catalogs.d.cts +3 -3
- package/dist/entries/picker-catalogs.d.ts +3 -3
- package/dist/entries/picker-catalogs.js +5 -5
- package/dist/entries/pickers.cjs +383 -383
- package/dist/entries/pickers.js +3 -3
- package/dist/entries/presets.cjs +55 -35
- package/dist/entries/presets.d.cts +29 -1
- package/dist/entries/presets.d.ts +29 -1
- package/dist/entries/presets.js +1 -1
- package/dist/entries/prompt-builder.cjs +76 -76
- package/dist/entries/prompt-builder.js +5 -5
- package/dist/{images-ChAOLY5V.d.cts → images-BQ1jUWyZ.d.cts} +32 -3
- package/dist/{images-ChAOLY5V.d.ts → images-BQ1jUWyZ.d.ts} +32 -3
- package/dist/index.cjs +657 -637
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +12 -12
- package/dist/{picker-catalogs-DzLCu6qQ.d.cts → picker-catalogs-BuMGOVWC.d.cts} +1 -1
- package/dist/{picker-catalogs-SDSWrufc.d.ts → picker-catalogs-CVQopXwp.d.ts} +1 -1
- package/package.json +2 -2
- package/src/__tests__/doctrine-roster-completeness.test.ts +3 -0
- package/src/__tests__/factory-preset-previews.test.ts +38 -0
- package/src/__tests__/factory-preset-shelves.test.ts +30 -0
- package/src/__tests__/picker-art-images.test.ts +54 -0
- package/src/__tests__/seedance-extend.test.ts +37 -23
- package/src/__tests__/video-reference-features.test.ts +7 -5
- package/src/__tests__/video-reference-seatless-assets.test.ts +132 -0
- package/src/factory-presets/index.ts +4 -0
- package/src/factory-presets/preview-files.generated.ts +472 -0
- package/src/factory-presets/previews.ts +17 -2
- package/src/factory-presets/shelves.ts +50 -0
- package/src/picker-art/images.ts +39 -2
- package/src/picker-catalogs.ts +4 -2
- package/src/prompt-wizard-categories.ts +15 -3
- package/src/provider-prompt-doctrine.ts +44 -5
- package/src/seatless-assets.ts +141 -0
- package/src/video-reference-resolver.ts +31 -4
- package/dist/chunk-5XPELUES.js.map +0 -1
- package/dist/chunk-E5EKWWOO.cjs.map +0 -1
- package/dist/chunk-EKRO42T3.cjs.map +0 -1
- package/dist/chunk-FFPFBNXH.cjs.map +0 -1
- package/dist/chunk-GRSKB3P7.cjs.map +0 -1
- package/dist/chunk-H322GC7M.js.map +0 -1
- package/dist/chunk-PHCYQWJP.js.map +0 -1
- package/dist/chunk-WQ7FWGZ2.js.map +0 -1
- package/dist/chunk-XR7RAHZI.js.map +0 -1
- package/dist/chunk-XRCK3BCF.cjs.map +0 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shelves an image-preset picker files the catalog's groups under: Video,
|
|
3
|
+
* Marketing, Photos & products, Creative, Life, then the pro catalog's groups
|
|
4
|
+
* (Studio · Pro). A group no shelf names goes on "more", so a group added to
|
|
5
|
+
* the catalog still shows. One mapping for every picker that shelves the
|
|
6
|
+
* catalog (a client's preset window, a menu that lists the categories, the
|
|
7
|
+
* catalog's group summary `GET /v1/node-presets/factory/groups`).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type PresetShelf = "video" | "marketing" | "photos" | "creative" | "life" | "more" | "pro"
|
|
11
|
+
|
|
12
|
+
/** The shelves in the order a picker shows them. */
|
|
13
|
+
export const PRESET_SHELF_ORDER: readonly PresetShelf[] = ["video", "marketing", "photos", "creative", "life", "more", "pro"]
|
|
14
|
+
|
|
15
|
+
/** The generate-image catalog's groups on the five named shelves, in the order they are shown. */
|
|
16
|
+
const NAMED_SHELVES: ReadonlyArray<readonly [string, PresetShelf]> = [
|
|
17
|
+
["YouTube", "video"],
|
|
18
|
+
["Shorts · TikTok", "video"],
|
|
19
|
+
["Social & Ads", "marketing"],
|
|
20
|
+
["Business", "marketing"],
|
|
21
|
+
["Business & Marketing", "marketing"],
|
|
22
|
+
["Personal Photos", "photos"],
|
|
23
|
+
["Products & Fashion", "photos"],
|
|
24
|
+
["Home & Spaces", "photos"],
|
|
25
|
+
["Text", "creative"],
|
|
26
|
+
["Content & Creative", "creative"],
|
|
27
|
+
["Layout", "creative"],
|
|
28
|
+
["Life & Events", "life"],
|
|
29
|
+
["Family & Events", "life"],
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
/** The pro catalog's groups (the canvas's and Studio's presets), on the Studio · Pro shelf. */
|
|
33
|
+
const PRO_GROUPS: ReadonlySet<string> = new Set([
|
|
34
|
+
"Reference Sheet", "Cast & Consistency", "Edit by Name", "Face Privacy", "Photography & Cinematic", "Characters", "Product & Commerce",
|
|
35
|
+
"Branding & Logos", "Marketing & Social", "Print & Posters", "Illustration & Art Styles", "Handmade & Stop-Motion", "Film & Storyboard",
|
|
36
|
+
"Architecture & Interiors", "Icons, Game Assets & Textures", "Diagrams & Infographics", "Portrait Transformations", "Stylized Subject", "Edits",
|
|
37
|
+
"Realism & Authentic",
|
|
38
|
+
])
|
|
39
|
+
|
|
40
|
+
const SHELF_OF: ReadonlyMap<string, PresetShelf> = new Map(NAMED_SHELVES)
|
|
41
|
+
|
|
42
|
+
/** The shelf a group goes on. */
|
|
43
|
+
export function presetShelf(group: string): PresetShelf {
|
|
44
|
+
return SHELF_OF.get(group) ?? (PRO_GROUPS.has(group) ? "pro" : "more")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Where a group stands among the named shelves' groups (the design's order), or -1 for one no named shelf lists. */
|
|
48
|
+
export function presetShelfRank(group: string): number {
|
|
49
|
+
return NAMED_SHELVES.findIndex(([name]) => name === group)
|
|
50
|
+
}
|
package/src/picker-art/images.ts
CHANGED
|
@@ -23,6 +23,23 @@ export interface PickerImageOptions {
|
|
|
23
23
|
* ask for it), and four URLs per option are dead weight to an agent (MCP).
|
|
24
24
|
*/
|
|
25
25
|
readonly srcSet?: boolean
|
|
26
|
+
/**
|
|
27
|
+
* A deployment's own pictures, in place of the platform's. When set, an
|
|
28
|
+
* option is pictured by what this returns for it and by nothing else — no
|
|
29
|
+
* self-hosted photo, emoji or flag, no render — so an option it has nothing
|
|
30
|
+
* for has no picture, and no Person / Styling topic has one (a topic is not
|
|
31
|
+
* an option). `baseUrl` and `lookPreviews` are not read. The package names no
|
|
32
|
+
* deployment picture itself: the API passes what the deployment registered.
|
|
33
|
+
*/
|
|
34
|
+
readonly own?: (catalogId: string, optionId: string) => PickerOwnPicture | undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A deployment's own picture for one picker option (`PickerImageOptions.own`). */
|
|
38
|
+
export interface PickerOwnPicture {
|
|
39
|
+
/** Absolute URL of the picture. */
|
|
40
|
+
readonly url: string
|
|
41
|
+
/** Absolute URL of its enlarged version, when the deployment gave one. */
|
|
42
|
+
readonly largeUrl?: string
|
|
26
43
|
}
|
|
27
44
|
|
|
28
45
|
/** Hosts whose Cloudflare zone resizes on the fly (`/cdn-cgi/image/`, `/cdn-cgi/media/`). */
|
|
@@ -65,6 +82,12 @@ export interface PickerOptionImage {
|
|
|
65
82
|
readonly url: string
|
|
66
83
|
/** `<url> 240w, <url> 480w, …` — present only for a render the CDN resizes (Nodaro Cloud), when asked for (`srcSet: true`). */
|
|
67
84
|
readonly srcSet?: string
|
|
85
|
+
/**
|
|
86
|
+
* The option's enlarged picture — present only for a deployment's own
|
|
87
|
+
* picture that has one (`PickerImageOptions.own`), when asked for
|
|
88
|
+
* (`srcSet: true`, the same request that asks for a srcset).
|
|
89
|
+
*/
|
|
90
|
+
readonly largeUrl?: string
|
|
68
91
|
}
|
|
69
92
|
|
|
70
93
|
/**
|
|
@@ -84,7 +107,10 @@ function renderImage(url: string, withSrcSet: boolean): PickerOptionImage | unde
|
|
|
84
107
|
}
|
|
85
108
|
|
|
86
109
|
/**
|
|
87
|
-
* The picture of one picker option, or undefined when it has none
|
|
110
|
+
* The picture of one picker option, or undefined when it has none.
|
|
111
|
+
*
|
|
112
|
+
* With `images.own` (a deployment's own pictures), what it returns for the
|
|
113
|
+
* option, and nothing else. Otherwise, the platform's:
|
|
88
114
|
*
|
|
89
115
|
* 1. the self-hosted character photos;
|
|
90
116
|
* 2. when renders are allowed (Nodaro Cloud), the music / voice pickers'
|
|
@@ -101,6 +127,7 @@ export function pickerOptionImage(
|
|
|
101
127
|
id: string,
|
|
102
128
|
images: PickerImageOptions,
|
|
103
129
|
): PickerOptionImage | undefined {
|
|
130
|
+
if (images.own) return ownImage(images.own(catalog.catalogId, id), images.srcSet === true)
|
|
104
131
|
const origin = originOf(images.baseUrl)
|
|
105
132
|
const photo = characterArtPath(catalog.catalogId, id)
|
|
106
133
|
if (photo) return origin ? { url: origin + photo } : undefined
|
|
@@ -116,6 +143,11 @@ export function pickerOptionImage(
|
|
|
116
143
|
return render ? renderImage(render, images.srcSet === true) : undefined
|
|
117
144
|
}
|
|
118
145
|
|
|
146
|
+
function ownImage(own: PickerOwnPicture | undefined, withLarge: boolean): PickerOptionImage | undefined {
|
|
147
|
+
if (!own) return undefined
|
|
148
|
+
return withLarge && own.largeUrl ? { url: own.url, largeUrl: own.largeUrl } : { url: own.url }
|
|
149
|
+
}
|
|
150
|
+
|
|
119
151
|
/** The absolute picture URL of one picker option, or undefined — `pickerOptionImage(...)?.url`. */
|
|
120
152
|
export function pickerOptionImageUrl(
|
|
121
153
|
catalog: { readonly nodeType: string; readonly catalogId: string },
|
|
@@ -126,8 +158,13 @@ export function pickerOptionImageUrl(
|
|
|
126
158
|
return pickerOptionImage(catalog, field, id, images)?.url
|
|
127
159
|
}
|
|
128
160
|
|
|
129
|
-
/**
|
|
161
|
+
/**
|
|
162
|
+
* The absolute URL of a Person / Styling topic's round picture, or undefined —
|
|
163
|
+
* always, with a deployment's own pictures (`images.own`): a topic is not an
|
|
164
|
+
* option, so it has none of them.
|
|
165
|
+
*/
|
|
130
166
|
export function pickerSectionImageUrl(sectionLabel: string, images: PickerImageOptions): string | undefined {
|
|
167
|
+
if (images.own) return undefined
|
|
131
168
|
const origin = originOf(images.baseUrl)
|
|
132
169
|
const path = characterSectionArtPath(sectionLabel)
|
|
133
170
|
return origin && path ? origin + path : undefined
|
package/src/picker-catalogs.ts
CHANGED
|
@@ -1088,8 +1088,9 @@ function projectOption(o: PickerOption, detail: PickerCatalogDetail, image?: Pic
|
|
|
1088
1088
|
}
|
|
1089
1089
|
|
|
1090
1090
|
function projectOptionFields(o: PickerOption, detail: PickerCatalogDetail, image?: PickerOptionImage): ProjectedPickerOption {
|
|
1091
|
-
// `imageUrl` / `imageSrcSet` (display data) ride at both
|
|
1092
|
-
// present only when the option has a picture (a srcset
|
|
1091
|
+
// `imageUrl` / `imageSrcSet` / `imageLargeUrl` (display data) ride at both
|
|
1092
|
+
// detail levels too, present only when the option has a picture (a srcset
|
|
1093
|
+
// only for a CDN render, a large picture only for a deployment's own).
|
|
1093
1094
|
const extras = {
|
|
1094
1095
|
...(o.motion ? { motion: o.motion } : {}),
|
|
1095
1096
|
// A style's media rides at both detail levels: a thin client filters its image style list by it.
|
|
@@ -1098,6 +1099,7 @@ function projectOptionFields(o: PickerOption, detail: PickerCatalogDetail, image
|
|
|
1098
1099
|
...(o.restyle === false ? { restyle: false as const } : {}),
|
|
1099
1100
|
...(image ? { imageUrl: image.url } : {}),
|
|
1100
1101
|
...(image?.srcSet ? { imageSrcSet: image.srcSet } : {}),
|
|
1102
|
+
...(image?.largeUrl ? { imageLargeUrl: image.largeUrl } : {}),
|
|
1101
1103
|
}
|
|
1102
1104
|
return detail === "full"
|
|
1103
1105
|
? {
|
|
@@ -182,6 +182,8 @@ export const PROVIDER_CAPABILITIES: Record<string, Record<string, string>> = {
|
|
|
182
182
|
"seedream-5-lite": "Lighter Seedream, faster artistic generation",
|
|
183
183
|
"seedream-5-pro": "Flagship Seedream — strongest instruction following, 2K via high quality",
|
|
184
184
|
"seedream-5-flash": "Fast, low-cost Seedream 5 — quick drafts and iteration at 1K or 2K",
|
|
185
|
+
"minimax-h3-image": "MiniMax H3 — readable text in the picture, up to about 16 MP at 4K, same price at 1K and 2K",
|
|
186
|
+
"seedream-5-5": "Seedream 5.5 — natural light, materials and skin, dense small text, transparent backgrounds; 1K / 1.5K / 2K",
|
|
185
187
|
"z-image": "Experimental, novel generation approaches",
|
|
186
188
|
"wan-2.7": "Wan 2.7 T2I — 1K/2K/4K, up to 9 ref images",
|
|
187
189
|
"wan-2.7-pro": "Wan 2.7 Pro T2I — higher quality, 1K/2K/4K",
|
|
@@ -210,6 +212,8 @@ export const PROVIDER_CAPABILITIES: Record<string, Record<string, string>> = {
|
|
|
210
212
|
"seedream-5-lite-i2i": "Light artistic transformation",
|
|
211
213
|
"seedream-5-pro-i2i": "Pro-grade instruction-based edits, multi-reference",
|
|
212
214
|
"seedream-5-flash-i2i": "Fast, low-cost Seedream edits — up to 10 input images",
|
|
215
|
+
"minimax-h3-image-i2i": "MiniMax H3 edits — up to 16 reference images in one edit, readable text, up to 4K",
|
|
216
|
+
"seedream-5-5-i2i": "Seedream 5.5 edits — round after round, the rest of the picture untouched; up to 10 input images",
|
|
213
217
|
"flux-kontext": "Character-consistent edits with reference awareness",
|
|
214
218
|
"flux-kontext-max": "Premium character-consistent editing",
|
|
215
219
|
"kontext-multi": "Multi-image Kontext via Replicate — up to 4 refs",
|
|
@@ -238,6 +242,8 @@ export const PROVIDER_CAPABILITIES: Record<string, Record<string, string>> = {
|
|
|
238
242
|
"seedream-5-lite-i2i": "Light artistic transformation",
|
|
239
243
|
"seedream-5-pro-i2i": "Pro-grade instruction-based edits, multi-reference",
|
|
240
244
|
"seedream-5-flash-i2i": "Fast, low-cost Seedream edits — up to 10 input images",
|
|
245
|
+
"minimax-h3-image-i2i": "MiniMax H3 edits — up to 16 reference images in one edit, readable text, up to 4K",
|
|
246
|
+
"seedream-5-5-i2i": "Seedream 5.5 edits — round after round, the rest of the picture untouched; up to 10 input images",
|
|
241
247
|
"flux-kontext": "Character-consistent edits with reference awareness",
|
|
242
248
|
"flux-kontext-max": "Premium character-consistent editing",
|
|
243
249
|
"kontext-multi": "Multi-image Kontext via Replicate — up to 4 refs",
|
|
@@ -279,7 +285,9 @@ export const PROVIDER_CAPABILITIES: Record<string, Record<string, string>> = {
|
|
|
279
285
|
"vidu-q4": "Vidu Q4 — start frame or up to 15 reference images + 3 MP3 audio refs, native audio, 3–16s, 540p–4K (image required)",
|
|
280
286
|
"kling-4": "Kling 4.0 — text, or up to 10 reference images / 5 videos / 7 characters / 3 voices; native audio, 3–30s, 720p/1080p",
|
|
281
287
|
"kling-4-keyframes": "Kling 4.0 Keyframes — start frame, up to 8 in-between frames and an end frame; native audio, 3–30s, 720p/1080p",
|
|
282
|
-
"
|
|
288
|
+
"kling-4-flash": "Kling 4.0 Flash — text, or up to 10 reference images / 5 videos / 7 characters (a voice clip per character); native audio, 3–20s, 720p",
|
|
289
|
+
"grok-imagine-video-1.5": "Grok Imagine 1.5 — image-to-video only; exact start and end frame (the same image loops), up to 4 in-between frames; requires an input image",
|
|
290
|
+
"pixverse-v6-max": "PixVerse V6 Max — cinematic text-to-video, 8 aspect ratios incl. 21:9, optional native audio, 1–15s, 360p–1080p",
|
|
283
291
|
},
|
|
284
292
|
"image-to-video": {
|
|
285
293
|
"minimax": "Versatile animation from still images",
|
|
@@ -318,7 +326,9 @@ export const PROVIDER_CAPABILITIES: Record<string, Record<string, string>> = {
|
|
|
318
326
|
"vidu-q4": "Vidu Q4 — animate a start frame, or direct up to 15 reference images + 3 MP3 audio refs; native audio, 3–16s, 540p–4K",
|
|
319
327
|
"kling-4": "Kling 4.0 — a start frame joins up to 10 reference images, 5 videos, 7 characters and 3 voices; native audio, 3–30s, 720p/1080p",
|
|
320
328
|
"kling-4-keyframes": "Kling 4.0 Keyframes — start frame, up to 8 in-between frames and an end frame; native audio, 3–30s, 720p/1080p",
|
|
321
|
-
"
|
|
329
|
+
"kling-4-flash": "Kling 4.0 Flash — a start frame alone is the exact opening frame; beside up to 10 reference images, 5 videos and 7 characters it joins them; native audio, 3–20s, 720p",
|
|
330
|
+
"grok-imagine-video-1.5": "Grok Imagine 1.5 — stylized animation, 1–15s, 480p/720p/1080p, start + up to 4 in-between + end frames (image required)",
|
|
331
|
+
"pixverse-v6-max": "PixVerse V6 Max — the image is the exact first frame and sets the shape; optional native audio, 1–15s, 360p–1080p",
|
|
322
332
|
},
|
|
323
333
|
"video-to-video": {
|
|
324
334
|
"wan": "Style transfer and video transformation",
|
|
@@ -335,7 +345,9 @@ export const PROVIDER_CAPABILITIES: Record<string, Record<string, string>> = {
|
|
|
335
345
|
"extend-video": {
|
|
336
346
|
"veo-extend": "Extend VEO-generated videos",
|
|
337
347
|
"runway-extend": "Extend Runway-generated videos",
|
|
338
|
-
"seedance-2-extend": "Extend ANY video by URL — seamless trim-stitched continuation with audio (Seedance 2.
|
|
348
|
+
"seedance-2-5-extend": "Extend ANY video by URL — seamless trim-stitched continuation with audio, 4–30s added (Seedance 2.5)",
|
|
349
|
+
"seedance-2-0-extend": "Extend ANY video by URL — seamless trim-stitched continuation with audio, 4–15s added (Seedance 2.0)",
|
|
350
|
+
"kling-4-extend": "Extend ANY video by URL — seamless trim-stitched continuation with audio, 3–30s added (Kling 4)",
|
|
339
351
|
},
|
|
340
352
|
"speech-to-video": {
|
|
341
353
|
"wan-speech": "Wan 2.2 speech-driven video generation",
|
|
@@ -302,7 +302,7 @@ const GROK_IMAGINE_DOCTRINE: ProviderPromptDoctrine = {
|
|
|
302
302
|
tips: [
|
|
303
303
|
"Keep prompts simple and direct — Subject + Action + Setting + Camera + Mood. Grok expands the prompt itself; over-specification fights the expander.",
|
|
304
304
|
"Image-to-video: the input image IS the first frame (composition, identity, and style are preserved) — describe the MOTION, don't re-describe the still.",
|
|
305
|
-
"Video 1.5: 1-15s (default 8), 480p/720p/1080p
|
|
305
|
+
"Video 1.5: 1-15s (default 8), 480p/720p/1080p; exact start and end frame (one image as both = seamless loop), up to 4 in-between frames. Native audio incl. dialogue — quote the line to have it spoken.",
|
|
306
306
|
"Aspect ratio applies to text runs (1:1/16:9/9:16/3:2/2:3/auto); a single input image locks the output to the image's own aspect.",
|
|
307
307
|
],
|
|
308
308
|
doctrine: `Prompt structure (xAI's guidance is minimal by design — the model auto-expands prompts):
|
|
@@ -311,18 +311,18 @@ descriptions of static/unchanged parts — spend the words on what MOVES.
|
|
|
311
311
|
|
|
312
312
|
**Image-to-video (the primary mode)**
|
|
313
313
|
- The input image is the FIRST FRAME, not a loose reference: composition, subject identity, and visual style carry over. Describe motion and camera only ("she turns toward the window as the camera slowly pushes in"); re-describing the still wastes adherence.
|
|
314
|
-
- grok-imagine-video-1.5
|
|
314
|
+
- grok-imagine-video-1.5 takes frames, not loose references: the start frame is the exact FIRST frame, an end frame the exact LAST frame, and the same image wired as both makes a seamless loop. Its Image References are up to 4 IN-BETWEEN frames, in order, placed evenly over the clip at whole seconds (one per second inside it — a 3s clip takes 2); extra frames are left out. Describe the motion that carries the shot from one frame to the next; the frames cannot be named in the prompt, so refer to them in words ("by the midpoint she has reached the door").
|
|
315
315
|
- grok-i2v with reference images: images are addressed \`@image(n)\` in array order. Your references come first (\`@image(1)\` is your first reference); a wired start frame is appended after them and the platform names it as the opening frame.
|
|
316
316
|
|
|
317
317
|
**Audio (video-1.5)**
|
|
318
|
-
- Native audio generates with the clip — background music, SFX, and lip-synced dialogue. Quote the spoken line; describe the music/SFX plainly.
|
|
318
|
+
- Native audio generates with the clip — background music, SFX, and lip-synced dialogue. Quote the spoken line; describe the music/SFX plainly. The platform offers no audio toggle for it — cue (or exclude) sound in the prompt text.
|
|
319
319
|
|
|
320
320
|
**Durations / tiers**
|
|
321
|
-
- grok-i2v: 6 or 10 seconds. grok-imagine-video-1.5: 1-15 seconds in 1s steps (default 8), 480p (default) / 720p / 1080p. Prompt cap 4096 chars — but shorter is better here.
|
|
321
|
+
- grok-i2v: 6 or 10 seconds. grok-imagine-video-1.5: 1-15 seconds in 1s steps (default 8), 480p (default) / 720p / 1080p. At 1080p it takes the start frame ALONE — a run with an end frame, a loop or in-between frames is refused before any credits are held, so use 720p for those. Prompt cap 4096 chars — but shorter is better here.
|
|
322
322
|
|
|
323
323
|
Sources: KIE Grok Imagine contracts (docs.kie.ai/market/grok-imagine/image-to-video,
|
|
324
324
|
docs.kie.ai/market/grok-imagine/1-5-preview), xAI Grok Imagine 1.5 release notes
|
|
325
|
-
(x.ai/news/grok-imagine-1-5). Captured 2026-08-09.`,
|
|
325
|
+
(x.ai/news/grok-imagine-1-5). Captured 2026-08-09; frames and in-between frames 2026-11-11.`,
|
|
326
326
|
}
|
|
327
327
|
|
|
328
328
|
const WAN_DOCTRINE: ProviderPromptDoctrine = {
|
|
@@ -494,6 +494,44 @@ Source: KIE Kling 4.0 market contracts (docs.kie.ai/market/kling/v4-reference-to
|
|
|
494
494
|
docs.kie.ai/market/kling/v4-keyframe-to-video), live-probed 2026-10-23.`,
|
|
495
495
|
}
|
|
496
496
|
|
|
497
|
+
// Kling 4.0 Flash — one id over KIE's two Flash endpoints, picked by what is
|
|
498
|
+
// wired (a start frame alone → keyframe endpoint; anything else → reference
|
|
499
|
+
// endpoint). KIE contract facts plus the platform-standard structure, as for
|
|
500
|
+
// Kling 4.0. The provider runner names subjects (`@Name`), binds each voice
|
|
501
|
+
// clip to a subject, and drops the `@` from any undeclared mention.
|
|
502
|
+
const KLING_4_FLASH_DOCTRINE: ProviderPromptDoctrine = {
|
|
503
|
+
providers: ["kling-4-flash"],
|
|
504
|
+
heading: "Kling 4.0 Flash (kling-4-flash)",
|
|
505
|
+
tips: [
|
|
506
|
+
"A start frame alone is the exact opening frame (@first_frame). Beside references it joins the reference images, like kling-4.",
|
|
507
|
+
"References: up to 10 images, 5 videos (2-20s together) and 7 characters (15 in all). Name each: @image_1, @video_1, a character's @Name.",
|
|
508
|
+
"No standalone voice: voice clip N is the voice of wired character N, if that character has 2-4 images (else the clip is dropped); write the character, never @audio_N.",
|
|
509
|
+
"3-20s (default 5), 720p only, native audio always on, no end frame. Prompt cap 8,000 chars. Aspect auto / 16:9 / 9:16 / 1:1 / 21:9 (a start frame alone sets its own shape).",
|
|
510
|
+
],
|
|
511
|
+
doctrine: `Prompt structure (no public Kling 4.0 Flash prompt guide is captured — the KIE API contract is
|
|
512
|
+
the doctrine source; platform-standard structure applies):
|
|
513
|
+
subject → action → scene/environment → lighting → camera movement → style → constraints.
|
|
514
|
+
|
|
515
|
+
**One model, two endpoints (picked by what is connected)**
|
|
516
|
+
- A start frame with nothing else — no reference image, no reference video, no character — runs keyframe-to-video: the frame is the exact opening frame and the clip takes its shape. Name it @first_frame.
|
|
517
|
+
- Anything else — text only, reference images, reference videos, characters, or a start frame beside any of them — runs reference-to-video. A start frame there joins the reference images and is named as the opening frame, as on kling-4.
|
|
518
|
+
|
|
519
|
+
**Naming materials in the prompt**
|
|
520
|
+
- @image_1 … for reference images, @video_1 … for reference videos, a character by its own name (@Maya). Kling refuses a prompt that names a material the request does not carry; the platform removes the "@" from such a mention so the run still starts.
|
|
521
|
+
- There are no standalone voices. Reference audio clip N becomes the voice of character N, in the order the characters are wired. A character needs 2-4 images to carry a voice: a clip whose character has one image, and any clip past the last character, is dropped (the run still starts). Write who speaks by the character's name ("@Maya says…"), never @audio_1.
|
|
522
|
+
|
|
523
|
+
**Limits**
|
|
524
|
+
- Reference images 10, reference videos 5, characters 7 — 15 in all. Reference videos together must be 2-20 seconds; a longer set is refused before the run starts.
|
|
525
|
+
- No end frame and no in-between frames: describe how the shot ends in words.
|
|
526
|
+
|
|
527
|
+
**Duration, resolution, audio**
|
|
528
|
+
- 3-20 seconds (default 5); 720p only; aspect auto (follows the materials, 16:9 with none) / 16:9 / 9:16 / 1:1 / 21:9. Prompts up to 8,000 characters.
|
|
529
|
+
- Native audio is always generated; keep-original-sound keeps a reference video's own audio. Not yet verified for scripted lines here, so plan key dialogue as a TTS + lip-sync pass.
|
|
530
|
+
|
|
531
|
+
Source: KIE Kling 4.0 Flash market contracts (docs.kie.ai/market/kling/v4-flash-reference-to-video,
|
|
532
|
+
docs.kie.ai/market/kling/v4-flash-keyframe-to-video), read 2026-11-11.`,
|
|
533
|
+
}
|
|
534
|
+
|
|
497
535
|
const HAPPYHORSE_DOCTRINE: ProviderPromptDoctrine = {
|
|
498
536
|
providers: ["happyhorse", "happyhorse-i2v", "happyhorse-ref2v", "happyhorse-edit"],
|
|
499
537
|
heading: "HappyHorse 1.1 (happyhorse, happyhorse-i2v, happyhorse-ref2v)",
|
|
@@ -555,6 +593,7 @@ export const PROVIDER_PROMPT_DOCTRINES: readonly ProviderPromptDoctrine[] = [
|
|
|
555
593
|
WAN_3_DOCTRINE,
|
|
556
594
|
VIDU_Q4_DOCTRINE,
|
|
557
595
|
KLING_4_DOCTRINE,
|
|
596
|
+
KLING_4_FLASH_DOCTRINE,
|
|
558
597
|
HAPPYHORSE_DOCTRINE,
|
|
559
598
|
RUNWAY_KIE_DOCTRINE,
|
|
560
599
|
]
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assets that take no image seat: the reference walk for a video model whose
|
|
3
|
+
* image references are its FRAMES (a `keyframes` model — Grok Imagine Video 1.5,
|
|
4
|
+
* Kling 4.0 Keyframes; decided 2026-11-11). Only what is wired into the node's
|
|
5
|
+
* Image References becomes a frame, so a wired character, a character extra or
|
|
6
|
+
* an entity attaches no picture and is numbered nowhere: no `@image_N` may point
|
|
7
|
+
* at a picture that is never sent.
|
|
8
|
+
*
|
|
9
|
+
* What a character still gives is its identity text — its name and description,
|
|
10
|
+
* the one line both reference formats render for a described reference — and a
|
|
11
|
+
* mention of it (`@kira:1`, `@kira:1:smile`) or a `{ref:<id>}` naming it reads as
|
|
12
|
+
* its name. A non-character extra or entity adds nothing.
|
|
13
|
+
*
|
|
14
|
+
* Pure: no I/O, no mutation of inputs. `resolveVideoReferenceCore` calls it when
|
|
15
|
+
* its caller passes `assetsTakeNoSeat`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { DEFAULT_USAGE_MODE, findCharacterMentionTokens, type UsageMode } from "@nodaro/shared/entities"
|
|
19
|
+
import type { ConnectedReference, DescribedReference } from "@nodaro/shared/types"
|
|
20
|
+
|
|
21
|
+
/** The fields of a video extra this walk reads (structural: `VideoExtraRef` fits). */
|
|
22
|
+
export interface SeatlessExtraRef {
|
|
23
|
+
id?: string
|
|
24
|
+
characterSlug?: string
|
|
25
|
+
description?: string
|
|
26
|
+
descriptionOverride?: string
|
|
27
|
+
usageMode?: UsageMode
|
|
28
|
+
elementInjection?: string | null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The fields of a character's metadata this walk reads (structural: `CharacterMeta` fits). */
|
|
32
|
+
export interface SeatlessCharacterMeta {
|
|
33
|
+
characterName?: string
|
|
34
|
+
defaultUsageMode?: UsageMode
|
|
35
|
+
canonicalDescription?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SeatlessAssets {
|
|
39
|
+
/** The prompt with every wired character's mention replaced by its name. */
|
|
40
|
+
prompt: string | undefined
|
|
41
|
+
/** One name + description per character with an identity to give, in wiring order. */
|
|
42
|
+
identities: DescribedReference[]
|
|
43
|
+
/** `{ref:<id>}` names for the character extras, which the walk seats nowhere. */
|
|
44
|
+
extraNamesById: Map<string, string>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The identity text a seatless character gives. Mirrors what the legacy
|
|
49
|
+
* canonical line puts after the name — the per-use override in every mode that
|
|
50
|
+
* renders a line, else the stored description in the modes that carry it, then
|
|
51
|
+
* the wired element injection — minus the usage directive, which instructs how
|
|
52
|
+
* to use a picture the model does not receive.
|
|
53
|
+
*/
|
|
54
|
+
function identityDescription(
|
|
55
|
+
mode: UsageMode,
|
|
56
|
+
override: string | null | undefined,
|
|
57
|
+
canonical: string | null | undefined,
|
|
58
|
+
injection: string | null | undefined,
|
|
59
|
+
): string {
|
|
60
|
+
if (mode === "none" || mode === "name") return ""
|
|
61
|
+
const parts: string[] = []
|
|
62
|
+
const o = override?.trim()
|
|
63
|
+
const c = canonical?.trim()
|
|
64
|
+
if (o) parts.push(o)
|
|
65
|
+
else if ((mode === "identical" || mode === "face-pose") && c) parts.push(c)
|
|
66
|
+
const i = injection?.trim()
|
|
67
|
+
if (i) parts.push(i)
|
|
68
|
+
return parts.join(". ")
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function resolveSeatlessAssets(args: {
|
|
72
|
+
prompt: string | undefined
|
|
73
|
+
/** Every wired character reference, BEFORE canonical suppression. */
|
|
74
|
+
wiredCharRefs: readonly ConnectedReference[]
|
|
75
|
+
/** Character slugs whose canonical fallback the user hid. */
|
|
76
|
+
suppressedCanonicalSlugs: ReadonlySet<string>
|
|
77
|
+
extraRefs?: readonly SeatlessExtraRef[]
|
|
78
|
+
lookupCharacterBySlug?: (slug: string) => SeatlessCharacterMeta | undefined
|
|
79
|
+
}): SeatlessAssets {
|
|
80
|
+
// The reference each wired character is described from: its canonical entry,
|
|
81
|
+
// else the first entry wired for it.
|
|
82
|
+
const bySlug = new Map<string, ConnectedReference>()
|
|
83
|
+
for (const r of args.wiredCharRefs) {
|
|
84
|
+
if (r.source !== "wired-character" || !r.characterSlug) continue
|
|
85
|
+
const known = bySlug.get(r.characterSlug)
|
|
86
|
+
if (!known || (known.variantSlug && !r.variantSlug)) bySlug.set(r.characterSlug, r)
|
|
87
|
+
}
|
|
88
|
+
const nameOf = (slug: string): string => {
|
|
89
|
+
const r = bySlug.get(slug)
|
|
90
|
+
return r?.defaultName || r?.characterSlug || slug
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Mentions read as the character's name (right to left, so offsets hold).
|
|
94
|
+
let prompt = args.prompt
|
|
95
|
+
const mentioned = new Set<string>()
|
|
96
|
+
if (prompt && bySlug.size > 0) {
|
|
97
|
+
const tokens = findCharacterMentionTokens(prompt, [...bySlug.keys()])
|
|
98
|
+
for (const t of [...tokens].sort((a, b) => b.offset - a.offset)) {
|
|
99
|
+
mentioned.add(t.characterSlug)
|
|
100
|
+
prompt = prompt.slice(0, t.offset) + nameOf(t.characterSlug) + prompt.slice(t.offset + t.token.length)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const identities: DescribedReference[] = []
|
|
105
|
+
const described = new Set<string>()
|
|
106
|
+
const describe = (slug: string, name: string, description: string): void => {
|
|
107
|
+
if (described.has(slug)) return
|
|
108
|
+
described.add(slug)
|
|
109
|
+
if (description) identities.push({ name, description })
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// A wired character gives its identity unless the user hid its canonical
|
|
113
|
+
// fallback and the prompt does not mention it either.
|
|
114
|
+
for (const [slug, r] of bySlug) {
|
|
115
|
+
const shown = !args.suppressedCanonicalSlugs.has(slug) || mentioned.has(slug)
|
|
116
|
+
if (!shown) continue
|
|
117
|
+
describe(
|
|
118
|
+
slug,
|
|
119
|
+
nameOf(slug),
|
|
120
|
+
identityDescription(r.defaultUsageMode ?? DEFAULT_USAGE_MODE, r.descriptionOverride, r.characterCanonicalDescription, r.elementInjection),
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// A character extra (a variant picked on the node) speaks for its character
|
|
125
|
+
// once; a non-character extra has no frame and no text.
|
|
126
|
+
const extraNamesById = new Map<string, string>()
|
|
127
|
+
for (const ex of args.extraRefs ?? []) {
|
|
128
|
+
if (!ex.characterSlug) continue
|
|
129
|
+
const meta = args.lookupCharacterBySlug?.(ex.characterSlug)
|
|
130
|
+
const name = bySlug.has(ex.characterSlug) ? nameOf(ex.characterSlug) : meta?.characterName || ex.characterSlug
|
|
131
|
+
if (ex.id) extraNamesById.set(ex.id, name)
|
|
132
|
+
const own = (ex.descriptionOverride ?? "").trim() || (ex.description ?? "").trim()
|
|
133
|
+
describe(
|
|
134
|
+
ex.characterSlug,
|
|
135
|
+
name,
|
|
136
|
+
identityDescription(ex.usageMode ?? meta?.defaultUsageMode ?? DEFAULT_USAGE_MODE, own, meta?.canonicalDescription, ex.elementInjection),
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { prompt, identities, extraNamesById }
|
|
141
|
+
}
|
|
@@ -50,6 +50,7 @@ import { REF_BINDING } from "./ref-binding.js"
|
|
|
50
50
|
import { resolveRefIdTokens } from "./ref-id-tokens.js"
|
|
51
51
|
import { insertBeforeStyleSection } from "./prompt-style-section.js"
|
|
52
52
|
import { tidyReferenceTokenGaps, unboundReferenceTokenText } from "./unbound-reference-tokens.js"
|
|
53
|
+
import { resolveSeatlessAssets } from "./seatless-assets.js"
|
|
53
54
|
|
|
54
55
|
// The binding surface string and the id-addressed token resolver live in their
|
|
55
56
|
// own modules (see them for the contracts); re-exported here so every existing
|
|
@@ -268,6 +269,15 @@ export interface ResolveVideoReferenceCoreArgs {
|
|
|
268
269
|
*/
|
|
269
270
|
videoCaptions?: readonly string[]
|
|
270
271
|
audioCaptions?: readonly string[]
|
|
272
|
+
/**
|
|
273
|
+
* No asset takes an image seat: set for a model whose image references are
|
|
274
|
+
* its FRAMES (the `keyframes` feature), where only what is wired into Image
|
|
275
|
+
* References is sent. Wired characters and extras attach no URL and are
|
|
276
|
+
* numbered nowhere; a character gives its name and description in both
|
|
277
|
+
* formats, and its mentions and `{ref:<id>}` read as its name. See
|
|
278
|
+
* `seatless-assets.ts`.
|
|
279
|
+
*/
|
|
280
|
+
assetsTakeNoSeat?: boolean
|
|
271
281
|
}
|
|
272
282
|
|
|
273
283
|
/** Result of the HYBRID mention pass — inline role phrases + surfaced opt-in
|
|
@@ -477,8 +487,21 @@ export function resolveVideoReferenceCore(
|
|
|
477
487
|
// below and the main assembly have to carry them. They attach no URL and claim
|
|
478
488
|
// no `@image_N` seat, so they take no part in the numbering walk. Empty for
|
|
479
489
|
// every caller that sends neither → every existing output is untouched.
|
|
490
|
+
// On a frames model (`assetsTakeNoSeat`) the wired characters join them as
|
|
491
|
+
// identity lines: no asset is seated, so the whole walk below is skipped.
|
|
492
|
+
const seatless = args.assetsTakeNoSeat === true
|
|
493
|
+
? resolveSeatlessAssets({
|
|
494
|
+
prompt: args.prompt,
|
|
495
|
+
wiredCharRefs: args.wiredCharRefs,
|
|
496
|
+
suppressedCanonicalSlugs: new Set(args.suppressedCanonicalCharacterIds ?? []),
|
|
497
|
+
extraRefs: args.extraRefs,
|
|
498
|
+
lookupCharacterBySlug: args.lookupCharacterBySlug,
|
|
499
|
+
})
|
|
500
|
+
: undefined
|
|
480
501
|
const describedAndCaptionLines = [
|
|
481
|
-
...renderDescribedReferenceLines(
|
|
502
|
+
...renderDescribedReferenceLines(
|
|
503
|
+
seatless ? [...seatless.identities, ...(args.describedReferences ?? [])] : args.describedReferences,
|
|
504
|
+
),
|
|
482
505
|
...renderReferenceCaptionLines(args.videoCaptions, args.audioCaptions, {
|
|
483
506
|
video: args.videoRefCount ?? 0,
|
|
484
507
|
audio: args.audioRefCount ?? 0,
|
|
@@ -510,6 +533,8 @@ export function resolveVideoReferenceCore(
|
|
|
510
533
|
for (const ex of args.extraRefs ?? []) {
|
|
511
534
|
if (ex.id && !nameByRefId.has(ex.id)) nameByRefId.set(ex.id, "")
|
|
512
535
|
}
|
|
536
|
+
// Seated nowhere, a character extra's `{ref:<id>}` reads as its character.
|
|
537
|
+
for (const [id, name] of seatless?.extraNamesById ?? []) nameByRefId.set(id, name)
|
|
513
538
|
for (const [id, name] of args.refNamesById ?? []) {
|
|
514
539
|
if (id) nameByRefId.set(id, name)
|
|
515
540
|
}
|
|
@@ -517,8 +542,10 @@ export function resolveVideoReferenceCore(
|
|
|
517
542
|
// recovered from URLs afterwards (the walk counts a duplicate-URL extra that
|
|
518
543
|
// `merged` dedups, so URL → index is ambiguous; id → position is not).
|
|
519
544
|
const slotByRefId = new Map<string, number>()
|
|
520
|
-
if (wiredCharRefs.length === 0 && !hasExtras) {
|
|
521
|
-
// No wired chars / extras
|
|
545
|
+
if (seatless || (wiredCharRefs.length === 0 && !hasExtras)) {
|
|
546
|
+
// No wired chars / extras — or none that may take a seat (`seatless`, whose
|
|
547
|
+
// characters already ride `describedAndCaptionLines` and whose mentions are
|
|
548
|
+
// already names) — but the node can still carry plain base reference
|
|
522
549
|
// images (leadingRefUrls), so `{image:N}` body tokens MUST still resolve. The
|
|
523
550
|
// count is the leading-ref count (or the legacy `imageRefCount` when no
|
|
524
551
|
// leading refs were passed); the leading URLs are returned for the payload.
|
|
@@ -528,7 +555,7 @@ export function resolveVideoReferenceCore(
|
|
|
528
555
|
// leadingRefUrls mode counts the leading refs; ordinalOffset mode counts the
|
|
529
556
|
// caller-owned leading refs the offset stands in for.
|
|
530
557
|
const resolved = resolveReferenceTokens(
|
|
531
|
-
resolveRefIdTokens(args.prompt, { slotById: slotByRefId, nameById: nameByRefId, imageCount: counts.image }),
|
|
558
|
+
resolveRefIdTokens(seatless ? seatless.prompt : args.prompt, { slotById: slotByRefId, nameById: nameByRefId, imageCount: counts.image }),
|
|
532
559
|
counts,
|
|
533
560
|
)
|
|
534
561
|
return {
|