@slatesvideo/shared 0.5.5 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -3
- package/dist/operations/index.d.ts +106 -19
- package/dist/operations/index.js +624 -172
- package/dist/prompts/character-sheet.d.ts +2 -1
- package/dist/prompts/character-sheet.js +82 -13
- package/dist/prompts/model-facts.d.ts +43 -1
- package/dist/prompts/model-facts.js +104 -2
- package/dist/prompts/prompting-tips.d.ts +1 -1
- package/dist/prompts/prompting-tips.js +208 -5
- package/dist/prompts/reference-composer.d.ts +21 -3
- package/dist/prompts/reference-composer.js +80 -10
- package/dist/prompts/reference-rules.d.ts +19 -2
- package/dist/prompts/reference-rules.js +18 -1
- package/dist/skills/content.js +8 -5
- package/exports/slates-prompt-builder/generated/SKILL.md +2 -2
- package/exports/slates-prompt-builder/generated/reference-character.md +9 -4
- package/exports/slates-prompt-builder/generated/reference-seedance.md +12 -1
- package/exports/slates-prompt-builder/generated/slates-prompt-builder-manifest.json +11 -11
- package/exports/slates-prompt-builder/generated/slates-prompt-builder.skill +0 -0
- package/package.json +1 -1
- package/skills/slates-character-identity.md +9 -4
- package/skills/slates-model-selection.md +39 -11
- package/skills/slates-prompting-elevenlabs.md +69 -0
- package/skills/slates-prompting-lip-sync.md +12 -14
- package/skills/slates-prompting-motion-transfer.md +18 -14
- package/skills/slates-prompting-seed-audio.md +110 -0
- package/skills/slates-prompting-seedance-2-5.md +215 -0
- package/skills/slates-prompting-seedance.md +17 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export type ReferenceKind = 'character' | 'environment' | 'style' | 'pinned' | 'first-frame' | 'last-frame' | 'video';
|
|
1
|
+
export type ReferenceKind = 'character' | 'environment' | 'style' | 'pinned' | 'first-frame' | 'last-frame' | 'video' | 'video-ref' | 'audio-ref';
|
|
2
2
|
export interface ReferenceMedia {
|
|
3
3
|
path: string;
|
|
4
|
-
mediaKind: 'image' | 'video';
|
|
4
|
+
mediaKind: 'image' | 'video' | 'audio';
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* A named bucket of reference media that can be @mentioned. The whole definition
|
|
@@ -22,8 +22,25 @@ export interface ComposedReferences {
|
|
|
22
22
|
prompt: string;
|
|
23
23
|
/** Free-reference image paths in cited order — flatten yields "image 1..N". */
|
|
24
24
|
orderedImagePaths: string[];
|
|
25
|
-
/** Video
|
|
25
|
+
/** Video paths in cited order — "video 1..M". Edit sources and reference
|
|
26
|
+
* clips SHARE this list and one counter, so a mixed state can never emit
|
|
27
|
+
* two "Video 1"s. */
|
|
26
28
|
orderedVideoPaths: string[];
|
|
29
|
+
/** Reference audio paths in cited order — "audio 1..K". */
|
|
30
|
+
orderedAudioPaths: string[];
|
|
31
|
+
/**
|
|
32
|
+
* Tokens written in the prompt that matched NO reference group, as authored
|
|
33
|
+
* (`'#noir'`, `'@bob'`), first-appearance order, deduped case-insensitively.
|
|
34
|
+
*
|
|
35
|
+
* 🚨 THIS FIELD EXISTS BECAUSE THE ALTERNATIVE IS A SILENT EDIT. A `#tag` with
|
|
36
|
+
* no matching style is DELETED from the text — a raw tag confuses every model,
|
|
37
|
+
* so removing it is right, but removing it without saying so changes what the
|
|
38
|
+
* user asked for behind their back. Prompt-transparency doctrine (slate
|
|
39
|
+
* `CLAUDE.md`) requires the surface to be able to say "this went nowhere", and
|
|
40
|
+
* this is the only record that the token was ever there. Callers that render a
|
|
41
|
+
* composed-prompt preview MUST surface it.
|
|
42
|
+
*/
|
|
43
|
+
unresolvedTokens: string[];
|
|
27
44
|
}
|
|
28
45
|
/**
|
|
29
46
|
* Compose the raw prompt (mentions intact) + an ORDERED list of reference groups
|
|
@@ -41,6 +58,7 @@ export interface ComposedReferences {
|
|
|
41
58
|
export interface ComposeOptions {
|
|
42
59
|
startImageNumber?: number;
|
|
43
60
|
startVideoNumber?: number;
|
|
61
|
+
startAudioNumber?: number;
|
|
44
62
|
}
|
|
45
63
|
export declare function composeReferences(rawPrompt: string, groups: ReferenceGroup[], opts?: ComposeOptions): ComposedReferences;
|
|
46
64
|
export interface KlingEditElement {
|
|
@@ -56,17 +56,28 @@ export function composeReferences(rawPrompt, groups, opts = {}) {
|
|
|
56
56
|
// ── 1. Assign global numbers by walking the list in order ──
|
|
57
57
|
let imageNum = opts.startImageNumber ?? 0;
|
|
58
58
|
let videoNum = opts.startVideoNumber ?? 0;
|
|
59
|
+
let audioNum = opts.startAudioNumber ?? 0;
|
|
59
60
|
const orderedImagePaths = [];
|
|
60
61
|
const orderedVideoPaths = [];
|
|
62
|
+
const orderedAudioPaths = [];
|
|
61
63
|
const numbered = groups.map((g) => {
|
|
62
64
|
const imageNums = [];
|
|
63
65
|
const videoNums = [];
|
|
66
|
+
const audioNums = [];
|
|
64
67
|
for (const m of g.media) {
|
|
65
|
-
|
|
68
|
+
// ONE video counter across the edit source and reference clips. They are
|
|
69
|
+
// mutually exclusive in practice, and a shared counter means a mixed
|
|
70
|
+
// state degrades to wrong-but-unambiguous rather than two "Video 1"s.
|
|
71
|
+
if (m.mediaKind === 'video' && (g.kind === 'video' || g.kind === 'video-ref')) {
|
|
66
72
|
videoNum += 1;
|
|
67
73
|
videoNums.push(videoNum);
|
|
68
74
|
orderedVideoPaths.push(m.path);
|
|
69
75
|
}
|
|
76
|
+
else if (m.mediaKind === 'audio' && g.kind === 'audio-ref') {
|
|
77
|
+
audioNum += 1;
|
|
78
|
+
audioNums.push(audioNum);
|
|
79
|
+
orderedAudioPaths.push(m.path);
|
|
80
|
+
}
|
|
70
81
|
else if (m.mediaKind === 'image' && isFreeRefImageKind(g.kind)) {
|
|
71
82
|
imageNum += 1;
|
|
72
83
|
imageNums.push(imageNum);
|
|
@@ -74,7 +85,7 @@ export function composeReferences(rawPrompt, groups, opts = {}) {
|
|
|
74
85
|
}
|
|
75
86
|
// first-frame / last-frame media: not numbered, not in the free-ref pool.
|
|
76
87
|
}
|
|
77
|
-
return { ...g, imageNums, videoNums };
|
|
88
|
+
return { ...g, imageNums, videoNums, audioNums };
|
|
78
89
|
});
|
|
79
90
|
// ── 2. Inline-name token groups in the prompt body ──
|
|
80
91
|
// For each character/environment group whose token appears in the prompt, the
|
|
@@ -87,21 +98,40 @@ export function composeReferences(rawPrompt, groups, opts = {}) {
|
|
|
87
98
|
byNorm.set(normToken(g.token), g);
|
|
88
99
|
const seenFirst = new Set();
|
|
89
100
|
const matchedInPrompt = new Set();
|
|
101
|
+
// Every token that named nothing, recorded as authored and deduped by the
|
|
102
|
+
// same normalisation used for matching. This is what makes the deletion
|
|
103
|
+
// below reportable instead of silent — see ComposedReferences.unresolvedTokens.
|
|
104
|
+
const unresolvedTokens = [];
|
|
105
|
+
const unresolvedSeen = new Set();
|
|
106
|
+
const noteUnresolved = (sigil, tok) => {
|
|
107
|
+
const key = normToken(`${sigil}${tok}`);
|
|
108
|
+
if (unresolvedSeen.has(key))
|
|
109
|
+
return;
|
|
110
|
+
unresolvedSeen.add(key);
|
|
111
|
+
unresolvedTokens.push(`${sigil}${tok}`);
|
|
112
|
+
};
|
|
90
113
|
// First strip "in/with the style of #tag" phrases so the style reads as a
|
|
91
|
-
// clean trailing clause, not a dangling preposition (legacy cleanPrompt
|
|
92
|
-
|
|
114
|
+
// clean trailing clause, not a dangling preposition (legacy cleanPrompt
|
|
115
|
+
// behaviour). An UNRESOLVED token in that phrase is stripped too: leaving the
|
|
116
|
+
// preposition behind ("…a portrait in the style of") was the worse half of the
|
|
117
|
+
// silent edit — the tag vanished a step later anyway and the sentence was left
|
|
118
|
+
// broken. Stripped or not, the token is reported.
|
|
119
|
+
let body = rawPrompt.replace(/\s+(with|in)\s+the\s+style\s+of\s+([@#])([\w-]+)/gi, (_full, _prep, sigil, tok) => {
|
|
93
120
|
const g = byNorm.get(normToken(`${sigil}${tok}`));
|
|
94
121
|
if (g && g.kind === 'style') {
|
|
95
122
|
matchedInPrompt.add(normToken(`${sigil}${tok}`));
|
|
96
123
|
return '';
|
|
97
124
|
}
|
|
98
|
-
|
|
125
|
+
noteUnresolved(sigil, tok);
|
|
126
|
+
return '';
|
|
99
127
|
});
|
|
100
128
|
body = body.replace(/([@#])([\w-]+)/g, (_full, _sigil, tok) => {
|
|
101
129
|
const key = normToken(`${_sigil}${tok}`);
|
|
102
130
|
const g = byNorm.get(key);
|
|
103
131
|
if (!g) {
|
|
104
132
|
// Unresolved token. A #unknown vanishes; an @unknown humanizes to words.
|
|
133
|
+
// Both are edits the user never asked for, so both are reported.
|
|
134
|
+
noteUnresolved(_sigil, tok);
|
|
105
135
|
return _sigil === '#' ? '' : humanizeToken(tok);
|
|
106
136
|
}
|
|
107
137
|
matchedInPrompt.add(key);
|
|
@@ -128,14 +158,52 @@ export function composeReferences(rawPrompt, groups, opts = {}) {
|
|
|
128
158
|
topKeys.push(`${noun} ${joinNums(g.videoNums)} ${verb} the motion source.`);
|
|
129
159
|
}
|
|
130
160
|
}
|
|
131
|
-
//
|
|
161
|
+
// Reference clips ("Video 2 is a provided reference."). This line SURVIVES
|
|
162
|
+
// the 2026-08-10 cull of null-role key lines because it is not one: the
|
|
163
|
+
// "motion source" wording above belongs to the edit path alone, so a video
|
|
164
|
+
// attachment has two possible roles and the sentence says which. The pinned
|
|
165
|
+
// IMAGE line had no such contrast partner and was deleted (see step 3).
|
|
166
|
+
for (const g of numbered) {
|
|
167
|
+
if (g.kind === 'video-ref' && g.videoNums.length > 0) {
|
|
168
|
+
const noun = g.videoNums.length === 1 ? 'Video' : 'Videos';
|
|
169
|
+
const tail = g.videoNums.length === 1 ? 'is a provided reference.' : 'are provided references.';
|
|
170
|
+
topKeys.push(`${noun} ${joinNums(g.videoNums)} ${tail}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Reference audio ("Audio 1 is a provided reference.").
|
|
132
174
|
for (const g of numbered) {
|
|
133
|
-
if (g.kind === '
|
|
134
|
-
const noun = g.
|
|
135
|
-
const tail = g.
|
|
136
|
-
topKeys.push(`${noun} ${joinNums(g.
|
|
175
|
+
if (g.kind === 'audio-ref' && g.audioNums.length > 0) {
|
|
176
|
+
const noun = g.audioNums.length === 1 ? 'Audio' : 'Audios';
|
|
177
|
+
const tail = g.audioNums.length === 1 ? 'is a provided reference.' : 'are provided references.';
|
|
178
|
+
topKeys.push(`${noun} ${joinNums(g.audioNums)} ${tail}`);
|
|
137
179
|
}
|
|
138
180
|
}
|
|
181
|
+
// 🚨 A PINNED REFERENCE IMAGE GETS NO KEY LINE, DELIBERATELY (2026-08-10).
|
|
182
|
+
// It used to emit "Image 1 is a provided reference." — the only branch here
|
|
183
|
+
// that assigns NO role, and therefore says nothing: every image in the request
|
|
184
|
+
// IS a provided reference, so the sentence restated the transport. Its
|
|
185
|
+
// siblings all carry information — "is the motion source" contrasts with a
|
|
186
|
+
// reference clip, "is the subject" / "is Marcus" name the thing, the style
|
|
187
|
+
// clause assigns a job. This one was the null member of that set, and unlike
|
|
188
|
+
// video there is no competing numbered image role for it to disambiguate
|
|
189
|
+
// against (frames ride dedicated slots and are never numbered).
|
|
190
|
+
//
|
|
191
|
+
// It was also actively wrong for the case pinned-first exists to serve: the
|
|
192
|
+
// desktop's `referenceGroups.ts` orders these images FIRST for the
|
|
193
|
+
// edit/injection case (the movie still you paint over reads as image 1) —
|
|
194
|
+
// where image 1 is the CANVAS, not a reference, and the line asserted the
|
|
195
|
+
// opposite.
|
|
196
|
+
//
|
|
197
|
+
// Every vendor assigns roles inline in natural language instead. Google's own
|
|
198
|
+
// multi-reference example is "the attached napkin sketch as the structure and
|
|
199
|
+
// the attached fabric sample as the texture"; ByteDance's is "use @Image 2 as
|
|
200
|
+
// the dormitory scene style reference". Nobody declares a null role. Our own
|
|
201
|
+
// skills say the same thing: "the model does NOT infer a reference's role from
|
|
202
|
+
// its position; the NAME carries it" — and this line had no name to carry.
|
|
203
|
+
//
|
|
204
|
+
// Do not add it back. Attaching an image already means "here is an image"; if
|
|
205
|
+
// a reference needs a role, it gets one from a role badge or from the user's
|
|
206
|
+
// own words, both of which compose into real sentences above.
|
|
139
207
|
// Token-less or not-in-prompt subjects/environments ("Image 1 is Marcus.").
|
|
140
208
|
for (const g of numbered) {
|
|
141
209
|
if ((g.kind === 'character' || g.kind === 'environment') && g.imageNums.length > 0) {
|
|
@@ -169,6 +237,8 @@ export function composeReferences(rawPrompt, groups, opts = {}) {
|
|
|
169
237
|
prompt: parts.join('\n\n'),
|
|
170
238
|
orderedImagePaths,
|
|
171
239
|
orderedVideoPaths,
|
|
240
|
+
orderedAudioPaths,
|
|
241
|
+
unresolvedTokens,
|
|
172
242
|
};
|
|
173
243
|
}
|
|
174
244
|
/** fal cap: max 4 combined element + style-image references per edit request. */
|
|
@@ -20,8 +20,25 @@ export declare const IDENTITY_LIGHTING = "flat, even, shadowless lighting";
|
|
|
20
20
|
* crushes hair and wardrobe silhouettes.** A deep neutral grey holds both.
|
|
21
21
|
*/
|
|
22
22
|
export declare const IDENTITY_PLATE_HEX = "#3a3a3c";
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* 🚨 THE HEX IS EMITTED WITHOUT ITS `#` AND THAT IS LOAD-BEARING (2026-07-30).
|
|
25
|
+
*
|
|
26
|
+
* `#` is a REFERENCE-TOKEN SIGIL in the desktop's prompt composer
|
|
27
|
+
* (`slate/src/shared/promptComposition.ts` — `/([@#])([\w-]+)/g`), and an
|
|
28
|
+
* unresolved `#token` is SILENTLY DELETED. So `#3a3a3c` never survived to any
|
|
29
|
+
* model: fal echoed back `deep neutral-grey background ()` on a real 2026-07-30
|
|
30
|
+
* request. The plate value has been doing nothing since the composer shipped.
|
|
31
|
+
*
|
|
32
|
+
* Writing it bare keeps the value in the prompt. `IDENTITY_PLATE_HEX` keeps its
|
|
33
|
+
* `#` because it is a colour constant and may have non-prompt consumers.
|
|
34
|
+
*
|
|
35
|
+
* GENERAL RULE FOR THIS FILE: never emit `#` or `@` into prompt text. Both are
|
|
36
|
+
* sigils downstream and both mangle silently — no error, no log, just missing
|
|
37
|
+
* words. HOW YOU'D KNOW THIS IS BEATEN: the composer stops treating bare
|
|
38
|
+
* `#hex` as a token, or starts leaving unresolved tokens intact.
|
|
39
|
+
*/
|
|
40
|
+
export declare const IDENTITY_BACKGROUND: string;
|
|
41
|
+
export declare const IDENTITY_LIGHTING_CLAUSE: string;
|
|
25
42
|
/**
|
|
26
43
|
* Craft clauses every identity reference wants — the eye and skin detail that
|
|
27
44
|
* survives downstream, plus the two "reads literally" guards. Crushed-black
|
|
@@ -96,7 +96,24 @@ export const IDENTITY_LIGHTING = 'flat, even, shadowless lighting';
|
|
|
96
96
|
* crushes hair and wardrobe silhouettes.** A deep neutral grey holds both.
|
|
97
97
|
*/
|
|
98
98
|
export const IDENTITY_PLATE_HEX = '#3a3a3c';
|
|
99
|
-
|
|
99
|
+
/**
|
|
100
|
+
* 🚨 THE HEX IS EMITTED WITHOUT ITS `#` AND THAT IS LOAD-BEARING (2026-07-30).
|
|
101
|
+
*
|
|
102
|
+
* `#` is a REFERENCE-TOKEN SIGIL in the desktop's prompt composer
|
|
103
|
+
* (`slate/src/shared/promptComposition.ts` — `/([@#])([\w-]+)/g`), and an
|
|
104
|
+
* unresolved `#token` is SILENTLY DELETED. So `#3a3a3c` never survived to any
|
|
105
|
+
* model: fal echoed back `deep neutral-grey background ()` on a real 2026-07-30
|
|
106
|
+
* request. The plate value has been doing nothing since the composer shipped.
|
|
107
|
+
*
|
|
108
|
+
* Writing it bare keeps the value in the prompt. `IDENTITY_PLATE_HEX` keeps its
|
|
109
|
+
* `#` because it is a colour constant and may have non-prompt consumers.
|
|
110
|
+
*
|
|
111
|
+
* GENERAL RULE FOR THIS FILE: never emit `#` or `@` into prompt text. Both are
|
|
112
|
+
* sigils downstream and both mangle silently — no error, no log, just missing
|
|
113
|
+
* words. HOW YOU'D KNOW THIS IS BEATEN: the composer stops treating bare
|
|
114
|
+
* `#hex` as a token, or starts leaving unresolved tokens intact.
|
|
115
|
+
*/
|
|
116
|
+
export const IDENTITY_BACKGROUND = `a plain, deep neutral-grey background (hex ${IDENTITY_PLATE_HEX.replace('#', '')})`;
|
|
100
117
|
export const IDENTITY_LIGHTING_CLAUSE = `Render on ${IDENTITY_BACKGROUND} with ${IDENTITY_LIGHTING} so the sheet captures the character's identity, not scene lighting.`;
|
|
101
118
|
/**
|
|
102
119
|
* Craft clauses every identity reference wants — the eye and skin detail that
|