@slatesvideo/shared 0.4.1 → 0.4.3
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/operations/index.d.ts +22 -3
- package/dist/operations/index.js +86 -19
- package/dist/prompts/content-policy.d.ts +31 -0
- package/dist/prompts/content-policy.js +108 -0
- package/dist/prompts/index.d.ts +2 -0
- package/dist/prompts/index.js +2 -0
- package/dist/prompts/reference-composer.d.ts +46 -0
- package/dist/prompts/reference-composer.js +175 -0
- package/dist/prompts/reference-rules.d.ts +1 -1
- package/dist/prompts/reference-rules.js +5 -5
- package/dist/skills/content.js +10 -8
- package/package.json +64 -59
- package/skills/slates-character-turnaround.md +2 -2
- package/skills/slates-content-policy.md +55 -0
- package/skills/slates-cost-discipline.md +8 -0
- package/skills/slates-one-prompt-film.md +1 -1
- package/skills/slates-project-organization.md +22 -0
- package/skills/slates-prompting-flux-2-max.md +4 -4
- package/skills/slates-prompting-kling-v3.md +2 -2
- package/skills/slates-prompting-nano-banana-2.md +4 -12
- package/skills/slates-prompting-seedance.md +17 -4
- package/skills/slates-prompting-veo-3.md +2 -2
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// Reference composition — the prompt-as-SSOT composer.
|
|
2
|
+
//
|
|
3
|
+
// THE PRINCIPLE: the prompt box is the single source of truth. The app's ENTIRE
|
|
4
|
+
// contribution to the model prompt is (a) translate @mentions/#tags into the
|
|
5
|
+
// lightweight "image N" naming the models actually parse, and (b) name
|
|
6
|
+
// token-less references (pinned images, frames, videos) by number. Nothing
|
|
7
|
+
// else — no role essays, no "ignore the outfit", no injected lighting/expression
|
|
8
|
+
// rules. What the user writes is what leads.
|
|
9
|
+
//
|
|
10
|
+
// This is the canonical implementation. It is mirrored byte-for-byte into the
|
|
11
|
+
// desktop app's `slate/src/shared/promptComposition.ts` (the desktop installs
|
|
12
|
+
// the published @slatesvideo/shared from npm and cannot file-import this source,
|
|
13
|
+
// so the mirror carries a header pointing here). Both the agent/MCP paths and
|
|
14
|
+
// the desktop generation + rail read from this one function, so the rail's badge
|
|
15
|
+
// numbers and the prompt's "image N" citations can never desync.
|
|
16
|
+
//
|
|
17
|
+
// Naming is the ONLY identity signal. Citing both of a subject's images as the
|
|
18
|
+
// SAME name ("Marcus (images 1 and 2)") tells the model they are ONE entity —
|
|
19
|
+
// which is what prevents a multi-image bucket from averaging into a blended
|
|
20
|
+
// face. This IS each model's own official consistency lever (NB2 "assign a
|
|
21
|
+
// distinct name", Seedance "Reference Subject_N in Image_N", Kling "reuse a fixed
|
|
22
|
+
// label verbatim"); the heavy role-essay block was the off-doctrine part.
|
|
23
|
+
// Normalize a name/token for matching: drop the sigil, lowercase, strip
|
|
24
|
+
// spaces/underscores/hyphens. "@big_red" / "@Big Red" / "#Big-Red" all collapse
|
|
25
|
+
// to the same key. Identical to the agent-side resolver's `norm`.
|
|
26
|
+
function normToken(s) {
|
|
27
|
+
return s.toLowerCase().replace(/[@#]/g, '').replace(/[\s_-]+/g, '');
|
|
28
|
+
}
|
|
29
|
+
// Free-reference IMAGE kinds get an "image N" number. Frames are transported in
|
|
30
|
+
// their own dedicated slots (start/last frame) by the per-model adapter and are
|
|
31
|
+
// NOT part of the free-reference numbering — see the reference-rules note that
|
|
32
|
+
// first/last-frame can't mix with free refs on models like Seedance.
|
|
33
|
+
function isFreeRefImageKind(kind) {
|
|
34
|
+
return kind === 'character' || kind === 'environment' || kind === 'style' || kind === 'pinned';
|
|
35
|
+
}
|
|
36
|
+
/** "image 5" / "images 5 and 6" / "images 5, 6 and 7" (lowercase, for inline use). */
|
|
37
|
+
function citeImages(nums) {
|
|
38
|
+
const noun = nums.length === 1 ? 'image' : 'images';
|
|
39
|
+
return `${noun} ${joinNums(nums)}`;
|
|
40
|
+
}
|
|
41
|
+
function joinNums(nums) {
|
|
42
|
+
if (nums.length === 1)
|
|
43
|
+
return String(nums[0]);
|
|
44
|
+
if (nums.length === 2)
|
|
45
|
+
return `${nums[0]} and ${nums[1]}`;
|
|
46
|
+
return `${nums.slice(0, -1).join(', ')} and ${nums[nums.length - 1]}`;
|
|
47
|
+
}
|
|
48
|
+
// Humanize an unresolved @token to plain words (preserve the legacy cleanPrompt
|
|
49
|
+
// fallback: @big_red → "Big Red", @forest3 → "Forest3"). Never send a raw token.
|
|
50
|
+
function humanizeToken(raw) {
|
|
51
|
+
return raw
|
|
52
|
+
.split(/[_-]/)
|
|
53
|
+
.map((w) => (w ? w.charAt(0).toUpperCase() + w.slice(1) : w))
|
|
54
|
+
.join(' ');
|
|
55
|
+
}
|
|
56
|
+
export function composeReferences(rawPrompt, groups, opts = {}) {
|
|
57
|
+
// ── 1. Assign global numbers by walking the list in order ──
|
|
58
|
+
let imageNum = opts.startImageNumber ?? 0;
|
|
59
|
+
let videoNum = opts.startVideoNumber ?? 0;
|
|
60
|
+
const orderedImagePaths = [];
|
|
61
|
+
const orderedVideoPaths = [];
|
|
62
|
+
const numbered = groups.map((g) => {
|
|
63
|
+
const imageNums = [];
|
|
64
|
+
const videoNums = [];
|
|
65
|
+
for (const m of g.media) {
|
|
66
|
+
if (m.mediaKind === 'video' && g.kind === 'video') {
|
|
67
|
+
videoNum += 1;
|
|
68
|
+
videoNums.push(videoNum);
|
|
69
|
+
orderedVideoPaths.push(m.path);
|
|
70
|
+
}
|
|
71
|
+
else if (m.mediaKind === 'image' && isFreeRefImageKind(g.kind)) {
|
|
72
|
+
imageNum += 1;
|
|
73
|
+
imageNums.push(imageNum);
|
|
74
|
+
orderedImagePaths.push(m.path);
|
|
75
|
+
}
|
|
76
|
+
// first-frame / last-frame media: not numbered, not in the free-ref pool.
|
|
77
|
+
}
|
|
78
|
+
return { ...g, imageNums, videoNums };
|
|
79
|
+
});
|
|
80
|
+
// ── 2. Inline-name token groups in the prompt body ──
|
|
81
|
+
// For each character/environment group whose token appears in the prompt, the
|
|
82
|
+
// FIRST occurrence becomes "Name (image N)"; later ones become just "Name".
|
|
83
|
+
// Style tokens are removed (a single trailing clause carries the style). Token
|
|
84
|
+
// groups NOT found in the prompt fall through to a key line in step 3.
|
|
85
|
+
const tokenGroups = numbered.filter((g) => g.token && (g.kind === 'character' || g.kind === 'environment' || g.kind === 'style'));
|
|
86
|
+
const byNorm = new Map();
|
|
87
|
+
for (const g of tokenGroups)
|
|
88
|
+
byNorm.set(normToken(g.token), g);
|
|
89
|
+
const seenFirst = new Set();
|
|
90
|
+
const matchedInPrompt = new Set();
|
|
91
|
+
// First strip "in/with the style of #tag" phrases so the style reads as a
|
|
92
|
+
// clean trailing clause, not a dangling preposition (legacy cleanPrompt behaviour).
|
|
93
|
+
let body = rawPrompt.replace(/\s+(with|in)\s+the\s+style\s+of\s+([@#])([\w-]+)/gi, (full, _prep, sigil, tok) => {
|
|
94
|
+
const g = byNorm.get(normToken(`${sigil}${tok}`));
|
|
95
|
+
if (g && g.kind === 'style') {
|
|
96
|
+
matchedInPrompt.add(normToken(`${sigil}${tok}`));
|
|
97
|
+
return '';
|
|
98
|
+
}
|
|
99
|
+
return full;
|
|
100
|
+
});
|
|
101
|
+
body = body.replace(/([@#])([\w-]+)/g, (_full, _sigil, tok) => {
|
|
102
|
+
const key = normToken(`${_sigil}${tok}`);
|
|
103
|
+
const g = byNorm.get(key);
|
|
104
|
+
if (!g) {
|
|
105
|
+
// Unresolved token. A #unknown vanishes; an @unknown humanizes to words.
|
|
106
|
+
return _sigil === '#' ? '' : humanizeToken(tok);
|
|
107
|
+
}
|
|
108
|
+
matchedInPrompt.add(key);
|
|
109
|
+
if (g.kind === 'style')
|
|
110
|
+
return ''; // styles never inline — trailing clause only
|
|
111
|
+
if (!seenFirst.has(key)) {
|
|
112
|
+
seenFirst.add(key);
|
|
113
|
+
return `${g.name} (${citeImages(g.imageNums)})`;
|
|
114
|
+
}
|
|
115
|
+
return g.name;
|
|
116
|
+
});
|
|
117
|
+
// Collapse the whitespace the token removals left behind.
|
|
118
|
+
body = body.replace(/[ \t]{2,}/g, ' ').replace(/\s+([,.;:!?])/g, '$1').trim();
|
|
119
|
+
// ── 3. Build the key lines for token-less / unmatched-token groups ──
|
|
120
|
+
// Video sources, pinned/base canvases, and picked subjects that have no token
|
|
121
|
+
// in the prompt each get ONE short neutral key line (never an essay). The user's
|
|
122
|
+
// own prompt supplies the intent ("this exact frame", "the man becomes…").
|
|
123
|
+
const topKeys = [];
|
|
124
|
+
// Video sources first ("Video 1 is the motion source.").
|
|
125
|
+
for (const g of numbered) {
|
|
126
|
+
if (g.kind === 'video' && g.videoNums.length > 0) {
|
|
127
|
+
const noun = g.videoNums.length === 1 ? 'Video' : 'Videos';
|
|
128
|
+
const verb = g.videoNums.length === 1 ? 'is' : 'are';
|
|
129
|
+
topKeys.push(`${noun} ${joinNums(g.videoNums)} ${verb} the motion source.`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Pinned/base references ("Image 1 is a provided reference.").
|
|
133
|
+
for (const g of numbered) {
|
|
134
|
+
if (g.kind === 'pinned' && g.imageNums.length > 0) {
|
|
135
|
+
const noun = g.imageNums.length === 1 ? 'Image' : 'Images';
|
|
136
|
+
const tail = g.imageNums.length === 1 ? 'is a provided reference.' : 'are provided references.';
|
|
137
|
+
topKeys.push(`${noun} ${joinNums(g.imageNums)} ${tail}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Token-less or not-in-prompt subjects/environments ("Image 1 is Marcus.").
|
|
141
|
+
for (const g of numbered) {
|
|
142
|
+
if ((g.kind === 'character' || g.kind === 'environment') && g.imageNums.length > 0) {
|
|
143
|
+
const tokenWasMatched = g.token && matchedInPrompt.has(normToken(g.token));
|
|
144
|
+
if (!tokenWasMatched) {
|
|
145
|
+
const noun = g.imageNums.length === 1 ? 'Image' : 'Images';
|
|
146
|
+
const verb = g.imageNums.length === 1 ? 'is' : 'are';
|
|
147
|
+
topKeys.push(`${noun} ${joinNums(g.imageNums)} ${verb} ${g.name}.`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// ── 4. Style trailing clause (one, at the end — style reads best last) ──
|
|
152
|
+
const styleNums = [];
|
|
153
|
+
for (const g of numbered) {
|
|
154
|
+
if (g.kind === 'style')
|
|
155
|
+
styleNums.push(...g.imageNums);
|
|
156
|
+
}
|
|
157
|
+
const styleClauses = [];
|
|
158
|
+
if (styleNums.length > 0) {
|
|
159
|
+
styleClauses.push(`Render in the visual style of ${citeImages(styleNums)}.`);
|
|
160
|
+
}
|
|
161
|
+
// ── 5. Assemble: [top key line] · [body] · [style clause], blank-line separated ──
|
|
162
|
+
const parts = [];
|
|
163
|
+
if (topKeys.length > 0)
|
|
164
|
+
parts.push(topKeys.join(' '));
|
|
165
|
+
if (body)
|
|
166
|
+
parts.push(body);
|
|
167
|
+
if (styleClauses.length > 0)
|
|
168
|
+
parts.push(styleClauses.join(' '));
|
|
169
|
+
return {
|
|
170
|
+
prompt: parts.join('\n\n'),
|
|
171
|
+
orderedImagePaths,
|
|
172
|
+
orderedVideoPaths,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=reference-composer.js.map
|
|
@@ -30,5 +30,5 @@ export declare const REFERENCE_RULES_HEADLINE = "Identity = a few flat-lit neutr
|
|
|
30
30
|
* plans/2026-06-25-slates-prompting-system-overhaul.md). The TARGET is to
|
|
31
31
|
* inject this text so it can't drift; today it's reconciled manually.
|
|
32
32
|
*/
|
|
33
|
-
export declare const REFERENCE_RULES_TEXT = "## Reference rules (how to use reference images)\n\nIdentity = a few flat-lit neutral angles; one reference per role, labeled; 2-4 refs not 12; describe environments instead of feeding a grid.\n\n1. **2-4 strong references beat both extremes.** Not 1 (warps toward itself), not 12 (averages worse). Start with 2-3 focused refs.\n2. **One reference per ROLE, labeled in the prompt.** Identity / style-grade / environment. The model does not infer roles from order \u2014 name each role in the prompt text. Same-role competitors drift.\n3. **Attach both sheets \u2014
|
|
33
|
+
export declare const REFERENCE_RULES_TEXT = "## Reference rules (how to use reference images)\n\nIdentity = a few flat-lit neutral angles; one reference per role, labeled; 2-4 refs not 12; describe environments instead of feeding a grid.\n\n1. **2-4 strong references beat both extremes.** Not 1 (warps toward itself), not 12 (averages worse). Start with 2-3 focused refs.\n2. **One reference per ROLE, labeled in the prompt.** Identity / style-grade / environment. The model does not infer roles from order \u2014 name each role in the prompt text. Same-role competitors drift.\n3. **Attach both sheets \u2014 NAME them as one entity, don't gate them.** Attach the full-body turnaround (body/proportion/outfit) AND the close-up expression sheet (high-res facial detail: eyes, skin, teeth, bone structure). NAME both inline as the same subject (\"Marcus (images 1 and 2)\") \u2014 the shared name tells the model they are ONE person, which is what stops the varied expressions from averaging the face. Do **not** inject a role essay (\"use for identity, ignore the outfit/lighting, render neutral\") \u2014 that drags the studio-lit sheet's wardrobe + lighting into the scene; the user's prompt owns wardrobe, expression, and lighting. Naming-as-one-entity IS each model's official lever (NB2 \"assign a distinct name\"; Seedance \"Reference Subject_N in Image_N\"; Kling \"reuse a fixed label verbatim\"). The trend is MORE references \u2014 addressing each by name is what makes many refs work.\n4. **Flat-light identity refs.** Prep identity refs with flat, even, shadowless lighting on a plain neutral background. Studio-lit / scene-lit sheets bleed their lighting into every generation (the studio-lit sheet \u2192 \"green-screen-pasted in front of mountains\" failure). Reference prep beats prompting here.\n5. **Environment: describe it, don't feed a grid.** Default to describing the location in words. Reserve an environment reference for a mandatory exact-match, and then use ONE clean establishing image with natural, even ambient lighting that reads as the location's real light, not a studio setup \u2014 never a multi-panel grid fed whole.\n6. **Grids: explore, don't input.** Use grids to explore compositions cheaply, then pick a cell. Never feed a grid back in as a reference \u2014 cells share a split detail budget and generate jointly, so flaws propagate.\n7. **Reuse the same refs across all shots.** Lock a set and reuse it; swapping refs mid-sequence causes drift.\n8. **Legible in-shot text \u2192 bake it into an image start frame, never trust text-to-video.** Animate from the locked frame.\n9. **I2V / own-footage superpower.** Restyle your own clip keeping the performance; delayed-VFX on \"video one\"; marker-object insertion; video-as-ref for a series. Describe ONLY what changes.\n10. **Style transform by natural language.** Default keeps the source's art style; an optional plain-text instruction transforms it (\"anime \u2192 real person\"). No preset pickers.";
|
|
34
34
|
//# sourceMappingURL=reference-rules.d.ts.map
|
|
@@ -25,10 +25,10 @@ export const REFERENCE_RULES = [
|
|
|
25
25
|
grade: 'community',
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
|
-
id: 'identity-
|
|
29
|
-
title: 'Attach both sheets —
|
|
30
|
-
rule: 'Attach BOTH the full-body turnaround (body/proportion/outfit) AND the close-up expression sheet (high-res facial detail).
|
|
31
|
-
why: '
|
|
28
|
+
id: 'identity-name-as-one-entity',
|
|
29
|
+
title: 'Attach both sheets — NAME them as one entity, don\'t gate them',
|
|
30
|
+
rule: 'Attach BOTH the full-body turnaround (body/proportion/outfit) AND the close-up expression sheet (high-res facial detail). NAME both inline as the SAME subject ("Marcus (images 1 and 2)") — that shared name is what tells the model they are ONE person and stops the varied expressions from averaging the face. Do NOT inject a role essay ("use for identity, ignore the outfit/lighting, render neutral"): the user\'s prompt owns wardrobe, expression, and lighting.',
|
|
31
|
+
why: 'Naming both images as one entity IS each model\'s OWN official consistency lever — NB2 "assign a distinct name to each character/object"; Seedance "Reference <Subject_N> in <Image_N>"; Kling "reuse a fixed label verbatim". The old heavy role-essay was the OFF-doctrine part: telling the model to "use for identity" while injecting "ignore the outfit" dragged the studio-lit sheet\'s wardrobe + lighting into scenes that explicitly wanted otherwise (the movie-still injection failure). The close-ups still carry far more facial signal (eyes, skin, teeth, bone structure) than a turnaround\'s postage-stamp faces, so attach both — the NAME, not an instruction, is what makes many refs work. The trend is MORE references (video/audio/3D into Seedance-class models), all addressed by name.',
|
|
32
32
|
grade: 'Eric-test',
|
|
33
33
|
},
|
|
34
34
|
{
|
|
@@ -109,7 +109,7 @@ ${REFERENCE_RULES_HEADLINE}
|
|
|
109
109
|
|
|
110
110
|
1. **2-4 strong references beat both extremes.** Not 1 (warps toward itself), not 12 (averages worse). Start with 2-3 focused refs.
|
|
111
111
|
2. **One reference per ROLE, labeled in the prompt.** Identity / style-grade / environment. The model does not infer roles from order — name each role in the prompt text. Same-role competitors drift.
|
|
112
|
-
3. **Attach both sheets —
|
|
112
|
+
3. **Attach both sheets — NAME them as one entity, don't gate them.** Attach the full-body turnaround (body/proportion/outfit) AND the close-up expression sheet (high-res facial detail: eyes, skin, teeth, bone structure). NAME both inline as the same subject ("Marcus (images 1 and 2)") — the shared name tells the model they are ONE person, which is what stops the varied expressions from averaging the face. Do **not** inject a role essay ("use for identity, ignore the outfit/lighting, render neutral") — that drags the studio-lit sheet's wardrobe + lighting into the scene; the user's prompt owns wardrobe, expression, and lighting. Naming-as-one-entity IS each model's official lever (NB2 "assign a distinct name"; Seedance "Reference Subject_N in Image_N"; Kling "reuse a fixed label verbatim"). The trend is MORE references — addressing each by name is what makes many refs work.
|
|
113
113
|
4. **Flat-light identity refs.** Prep identity refs with ${IDENTITY_LIGHTING} on a plain neutral background. Studio-lit / scene-lit sheets bleed their lighting into every generation (the studio-lit sheet → "green-screen-pasted in front of mountains" failure). Reference prep beats prompting here.
|
|
114
114
|
5. **Environment: describe it, don't feed a grid.** Default to describing the location in words. Reserve an environment reference for a mandatory exact-match, and then use ONE clean establishing image with ${ENVIRONMENT_NATURAL_LIGHT} — never a multi-panel grid fed whole.
|
|
115
115
|
6. **Grids: explore, don't input.** Use grids to explore compositions cheaply, then pick a cell. Never feed a grid back in as a reference — cells share a split detail budget and generate jointly, so flaws propagate.
|