@slatesvideo/shared 0.6.10 → 0.7.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/README.md +1 -1
- package/dist/auth.js +2 -2
- package/dist/clients/cloud.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -1
- package/dist/manual/content.d.ts +1 -1
- package/dist/manual/content.js +1 -1
- package/dist/operations/index.d.ts +817 -16
- package/dist/operations/index.js +1423 -372
- package/dist/operations/surface.d.ts +3 -1
- package/dist/operations/surface.js +37 -10
- package/dist/prompts/ad-presets.d.ts +77 -0
- package/dist/prompts/ad-presets.js +43 -0
- package/dist/prompts/agent-doctrine.js +5 -4
- package/dist/prompts/banned-tokens.d.ts +4 -29
- package/dist/prompts/banned-tokens.js +29 -204
- package/dist/prompts/craft-cards.js +2 -2
- package/dist/prompts/generation-policy.d.ts +41 -0
- package/dist/prompts/generation-policy.js +53 -0
- package/dist/prompts/guide-retrieval.d.ts +9 -0
- package/dist/prompts/guide-retrieval.js +53 -0
- package/dist/prompts/index.d.ts +1 -0
- package/dist/prompts/index.js +1 -0
- package/dist/prompts/model-capabilities.d.ts +18 -1
- package/dist/prompts/model-capabilities.js +72 -19
- package/dist/prompts/model-facts.d.ts +59 -0
- package/dist/prompts/model-facts.js +121 -15
- package/dist/prompts/partials.generated.js +8 -2
- package/dist/prompts/prompting-tips.d.ts +1 -1
- package/dist/prompts/prompting-tips.js +63 -18
- package/dist/prompts/reference-composer.d.ts +2 -0
- package/dist/prompts/reference-composer.js +51 -50
- package/dist/prompts/script-document.d.ts +165 -0
- package/dist/prompts/script-document.js +11 -0
- package/dist/prompts/shot-grammar.d.ts +4 -4
- package/dist/prompts/shot-grammar.js +3 -3
- package/dist/prompts/shot-spec.d.ts +13 -0
- package/dist/prompts/shot-spec.js +23 -5
- package/dist/skills/content.js +26 -23
- package/dist/update-check.d.ts +22 -0
- package/dist/update-check.js +109 -0
- package/exports/slates-chatgpt-images/generated/SKILL.md +107 -0
- package/exports/slates-chatgpt-images/generated/slates-chatgpt-images.skill +0 -0
- package/exports/slates-prompt-builder/generated/SKILL.md +3 -3
- package/exports/slates-prompt-builder/generated/reference-character.md +9 -1
- package/exports/slates-prompt-builder/generated/reference-kling.md +3 -3
- package/exports/slates-prompt-builder/generated/reference-nano-banana.md +22 -10
- package/exports/slates-prompt-builder/generated/reference-seedance.md +4 -4
- package/exports/slates-prompt-builder/generated/slates-prompt-builder-manifest.json +17 -17
- package/exports/slates-prompt-builder/generated/slates-prompt-builder.skill +0 -0
- package/package.json +10 -4
- package/skills/_partials/cinematic-card.md +8 -0
- package/skills/_partials/cinematic-routes-short.md +2 -0
- package/skills/_partials/cinematic-tips-short.md +2 -0
- package/skills/_partials/decision-log.md +1 -13
- package/skills/_partials/image-defaults.md +11 -0
- package/skills/_partials/lens-video-split.md +1 -0
- package/skills/_partials/reference-rules-core.md +1 -1
- package/skills/_partials/sheet-tool-defaults.md +6 -0
- package/skills/slates-character-identity.md +9 -1
- package/skills/slates-chatgpt-images.md +107 -0
- package/skills/slates-cinematic-look.md +237 -0
- package/skills/slates-cost-discipline.md +18 -12
- package/skills/slates-direct-response-ad.md +13 -53
- package/skills/slates-edit-and-iterate.md +1 -1
- package/skills/slates-model-selection.md +139 -133
- package/skills/slates-one-prompt-film.md +38 -95
- package/skills/slates-project-organization.md +7 -3
- package/skills/slates-prompting-flux-2-max.md +15 -4
- package/skills/slates-prompting-gpt-image-2-5.md +41 -28
- package/skills/slates-prompting-kling-v3.md +3 -3
- package/skills/slates-prompting-lip-sync.md +1 -1
- package/skills/slates-prompting-minimax-h3.md +30 -17
- package/skills/slates-prompting-motion-transfer.md +1 -1
- package/skills/slates-prompting-nano-banana-2.md +24 -11
- package/skills/slates-prompting-seedance-2-5.md +12 -12
- package/skills/slates-prompting-seedance.md +5 -5
- package/skills/slates-prompting-seedream-5-lite.md +14 -3
- package/skills/slates-prompting-veo-3.md +1 -1
- package/skills/slates-script-craft.md +45 -0
- package/skills/slates-shot-variety.md +11 -40
- package/skills/slates-storyboard-from-script.md +14 -66
- package/skills/slates-style-prompting.md +54 -54
- package/skills/slates-ugc-influencer-ad.md +32 -309
- package/skills/slates-vision-feedback-loop.md +2 -1
|
@@ -196,4 +196,6 @@ export declare const KLING_EDIT_MAX_REFS = 4;
|
|
|
196
196
|
* trimmed first — subjects are the feature)
|
|
197
197
|
*/
|
|
198
198
|
export declare function composeKlingEdit(rawPrompt: string, groups: ReferenceGroup[]): KlingEditComposition;
|
|
199
|
+
/** No-reference paths use the same composer and preserve authored prose. */
|
|
200
|
+
export declare function cleanPrompt(userPrompt: string): string;
|
|
199
201
|
//# sourceMappingURL=reference-composer.d.ts.map
|
|
@@ -19,11 +19,12 @@
|
|
|
19
19
|
// is each model's own official consistency lever (NB2 "assign a
|
|
20
20
|
// distinct name", Seedance "Reference Subject_N in Image_N", Kling "reuse a fixed
|
|
21
21
|
// label verbatim"); the heavy role-essay block was the off-doctrine part.
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
22
|
+
// A mention's matching key: lowercase, spaces/underscores/hyphens stripped, and
|
|
23
|
+
// the SIGIL KEPT. "@big_red" / "@Big Red" / "@Big-Red" are one key, and "@red"
|
|
24
|
+
// and "#red" are two: names are unique per sigil, so a subject and a look may
|
|
25
|
+
// share one, and a sigil-free key bound both mentions to whichever came last.
|
|
25
26
|
function normToken(s) {
|
|
26
|
-
return s.toLowerCase().replace(/[
|
|
27
|
+
return s.toLowerCase().replace(/[\s_-]+/g, '');
|
|
27
28
|
}
|
|
28
29
|
// Free-reference IMAGE kinds get an "image N" number. Frames are transported in
|
|
29
30
|
// their own dedicated slots (start/last frame) by the per-model adapter and are
|
|
@@ -179,7 +180,7 @@ export function composeReferences(rawPrompt, groups, opts = {}) {
|
|
|
179
180
|
// ── 2. Inline-name token groups in the prompt body ──
|
|
180
181
|
// For each character/environment group whose token appears in the prompt, the
|
|
181
182
|
// FIRST occurrence becomes "Name (image N)"; later ones become just "Name".
|
|
182
|
-
// Style tokens
|
|
183
|
+
// Style tokens become image citations at the user's chosen binding site. Token
|
|
183
184
|
// groups NOT found in the prompt fall through to a key line in step 3.
|
|
184
185
|
const tokenGroups = numbered.filter((g) => g.token && (g.kind === 'character' || g.kind === 'environment' || g.kind === 'style'));
|
|
185
186
|
const byNorm = new Map();
|
|
@@ -204,20 +205,7 @@ export function composeReferences(rawPrompt, groups, opts = {}) {
|
|
|
204
205
|
unresolvedSeen.add(key);
|
|
205
206
|
unresolvedTokens.push(`${sigil}${tok}`);
|
|
206
207
|
};
|
|
207
|
-
|
|
208
|
-
// clean trailing clause, not a dangling preposition (legacy cleanPrompt
|
|
209
|
-
// behaviour). ONLY when the tag resolves: an unresolved one leaves the whole
|
|
210
|
-
// phrase exactly as authored and falls through to the token pass below, which
|
|
211
|
-
// reports it and sends it as written.
|
|
212
|
-
let body = rawPrompt.replace(/\s+(with|in)\s+the\s+style\s+of\s+([@#])([\w-]+(?:\.[\w-]+)*)/gi, (_full, _prep, sigil, tok) => {
|
|
213
|
-
const g = byNorm.get(normToken(`${sigil}${tok}`));
|
|
214
|
-
if (g && g.kind === 'style') {
|
|
215
|
-
matchedInPrompt.add(normToken(`${sigil}${tok}`));
|
|
216
|
-
return '';
|
|
217
|
-
}
|
|
218
|
-
return _full;
|
|
219
|
-
});
|
|
220
|
-
body = body.replace(TOKEN_RE, (_full, _sigil, tok) => {
|
|
208
|
+
const body = rawPrompt.replace(TOKEN_RE, (_full, _sigil, tok) => {
|
|
221
209
|
const key = normToken(`${_sigil}${tok}`);
|
|
222
210
|
const g = byNorm.get(key);
|
|
223
211
|
if (!g) {
|
|
@@ -227,7 +215,7 @@ export function composeReferences(rawPrompt, groups, opts = {}) {
|
|
|
227
215
|
}
|
|
228
216
|
matchedInPrompt.add(key);
|
|
229
217
|
if (g.kind === 'style')
|
|
230
|
-
return
|
|
218
|
+
return g.imageNums.length ? citeImages(g.imageNums) : g.name;
|
|
231
219
|
if (!seenFirst.has(key)) {
|
|
232
220
|
seenFirst.add(key);
|
|
233
221
|
// 🚨 ONE BINDING SITE PER ENTITY, CARRYING EVERY MEDIUM SHE OWNS
|
|
@@ -257,8 +245,13 @@ export function composeReferences(rawPrompt, groups, opts = {}) {
|
|
|
257
245
|
}
|
|
258
246
|
return g.name;
|
|
259
247
|
});
|
|
260
|
-
//
|
|
261
|
-
|
|
248
|
+
// Literal citations are authored bindings too. Do not add a competing role
|
|
249
|
+
// sentence when the user already describes what that image supplies.
|
|
250
|
+
const citedImages = new Set();
|
|
251
|
+
for (const match of body.matchAll(/\bimages?\s+(\d+(?:\s*(?:,\s*(?:(?:and|&)\s*)?|(?:and|&)\s*)\d+)*)\b/gi)) {
|
|
252
|
+
for (const n of match[1].match(/\d+/g) ?? [])
|
|
253
|
+
citedImages.add(Number(n));
|
|
254
|
+
}
|
|
262
255
|
// ── 3. Build the key lines for token-less / unmatched-token groups ──
|
|
263
256
|
// Video sources, pinned/base canvases, and picked subjects that have no token
|
|
264
257
|
// in the prompt each get ONE short neutral key line (never an essay). The user's
|
|
@@ -314,10 +307,11 @@ export function composeReferences(rawPrompt, groups, opts = {}) {
|
|
|
314
307
|
for (const g of numbered) {
|
|
315
308
|
if ((g.kind === 'character' || g.kind === 'environment') && g.imageNums.length > 0) {
|
|
316
309
|
const tokenWasMatched = g.token && matchedInPrompt.has(normToken(g.token));
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const
|
|
320
|
-
|
|
310
|
+
const unmentioned = g.imageNums.filter((n) => !citedImages.has(n));
|
|
311
|
+
if (!tokenWasMatched && unmentioned.length) {
|
|
312
|
+
const noun = unmentioned.length === 1 ? 'Image' : 'Images';
|
|
313
|
+
const verb = unmentioned.length === 1 ? 'is' : 'are';
|
|
314
|
+
topKeys.push(`${noun} ${joinNums(unmentioned)} ${verb} ${g.name}.`);
|
|
321
315
|
}
|
|
322
316
|
}
|
|
323
317
|
}
|
|
@@ -419,11 +413,11 @@ export function composeReferences(rawPrompt, groups, opts = {}) {
|
|
|
419
413
|
// `Audio 1` sitting mid-sentence among lowercase `image 1`s; moving the
|
|
420
414
|
// binding inline removed the reason for the exception along with the
|
|
421
415
|
// exception.
|
|
422
|
-
// ── 4.
|
|
416
|
+
// ── 4. Fallback for style attachments the user has not cited ──
|
|
423
417
|
const styleNums = [];
|
|
424
418
|
for (const g of numbered) {
|
|
425
419
|
if (g.kind === 'style')
|
|
426
|
-
styleNums.push(...g.imageNums);
|
|
420
|
+
styleNums.push(...g.imageNums.filter((n) => !citedImages.has(n)));
|
|
427
421
|
}
|
|
428
422
|
const styleClauses = [];
|
|
429
423
|
if (styleNums.length > 0) {
|
|
@@ -475,10 +469,12 @@ export function composeVoiceCitations(rawPrompt, voices) {
|
|
|
475
469
|
if (v?.token)
|
|
476
470
|
byNorm.set(normToken(v.token), { n: i + 1, name: v.name });
|
|
477
471
|
});
|
|
472
|
+
// The one mention grammar (TOKEN_RE), so `joe@sarah.com` and `@sarah.extra`
|
|
473
|
+
// stay prose. A `#` token is a look, never a voice.
|
|
478
474
|
const seen = new Set();
|
|
479
|
-
const body = rawPrompt.replace(
|
|
480
|
-
const key = normToken(
|
|
481
|
-
const v = byNorm.get(key);
|
|
475
|
+
const body = rawPrompt.replace(TOKEN_RE, (full, sigil, tok) => {
|
|
476
|
+
const key = normToken(`${sigil}${tok}`);
|
|
477
|
+
const v = sigil === '@' ? byNorm.get(key) : undefined;
|
|
482
478
|
if (!v)
|
|
483
479
|
return full;
|
|
484
480
|
if (seen.has(key))
|
|
@@ -525,7 +521,12 @@ export function composeKlingEdit(rawPrompt, groups) {
|
|
|
525
521
|
const elements = [];
|
|
526
522
|
const styleImages = [];
|
|
527
523
|
const styleNums = [];
|
|
528
|
-
|
|
524
|
+
// Each mention's citation, by key. The prompt is walked ONCE with the one
|
|
525
|
+
// mention grammar (TOKEN_RE), so `joe@marcus.com` and `@marcus.extra` stay
|
|
526
|
+
// prose, as they do in `composeReferences`.
|
|
527
|
+
const citations = new Map();
|
|
528
|
+
const inPrompt = new Set([...rawPrompt.matchAll(TOKEN_RE)].map((t) => normToken(`${t[1]}${t[2]}`)));
|
|
529
|
+
const keyLines = [];
|
|
529
530
|
// Subjects first — they own the @ElementN numbering.
|
|
530
531
|
const subjectGroups = groups.filter((g) => (g.kind === 'character' || g.kind === 'environment') && g.media.some((m) => m.mediaKind === 'image'));
|
|
531
532
|
for (const g of subjectGroups) {
|
|
@@ -536,20 +537,13 @@ export function composeKlingEdit(rawPrompt, groups) {
|
|
|
536
537
|
continue;
|
|
537
538
|
const n = elements.length + 1;
|
|
538
539
|
elements.push({ frontal: imgs[0], angles: imgs.slice(1, 4), name: g.name });
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
}
|
|
546
|
-
else {
|
|
547
|
-
body = `${body}\n@Element${n} is ${g.name}.`;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
else {
|
|
551
|
-
body = `${body}\n@Element${n} is ${g.name}.`;
|
|
552
|
-
}
|
|
540
|
+
// Every mention of the subject becomes its element citation; a subject the
|
|
541
|
+
// prompt never names gets a key line.
|
|
542
|
+
const key = g.token ? normToken(g.token) : null;
|
|
543
|
+
if (key && inPrompt.has(key) && !citations.has(key))
|
|
544
|
+
citations.set(key, `@Element${n}`);
|
|
545
|
+
else
|
|
546
|
+
keyLines.push(`@Element${n} is ${g.name}.`);
|
|
553
547
|
}
|
|
554
548
|
// Style / pinned refs take the remaining slots as @ImageN.
|
|
555
549
|
for (const g of groups) {
|
|
@@ -564,12 +558,15 @@ export function composeKlingEdit(rawPrompt, groups) {
|
|
|
564
558
|
const n = styleImages.length;
|
|
565
559
|
if (g.kind === 'style')
|
|
566
560
|
styleNums.push(n);
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
561
|
+
// A group's mention cites its FIRST image.
|
|
562
|
+
const key = g.token ? normToken(g.token) : null;
|
|
563
|
+
if (key && !citations.has(key))
|
|
564
|
+
citations.set(key, `@Image${n}`);
|
|
571
565
|
}
|
|
572
566
|
}
|
|
567
|
+
let body = rawPrompt.replace(TOKEN_RE, (full, sigil, tok) => citations.get(normToken(`${sigil}${tok}`)) ?? full);
|
|
568
|
+
for (const line of keyLines)
|
|
569
|
+
body = `${body}\n${line}`;
|
|
573
570
|
if (styleNums.length > 0) {
|
|
574
571
|
const cites = styleNums.map((n) => `@Image${n}`).join(' and ');
|
|
575
572
|
body = `${body}\nApply the visual style of ${cites}.`;
|
|
@@ -584,4 +581,8 @@ export function composeKlingEdit(rawPrompt, groups) {
|
|
|
584
581
|
styleImages,
|
|
585
582
|
};
|
|
586
583
|
}
|
|
584
|
+
/** No-reference paths use the same composer and preserve authored prose. */
|
|
585
|
+
export function cleanPrompt(userPrompt) {
|
|
586
|
+
return composeReferences(userPrompt, []).prompt;
|
|
587
|
+
}
|
|
587
588
|
//# sourceMappingURL=reference-composer.js.map
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/** Script document wire contract. Spoken text has one home: the scene string. */
|
|
2
|
+
export interface Block {
|
|
3
|
+
id: string;
|
|
4
|
+
kind: 'paragraph' | 'heading' | 'direction';
|
|
5
|
+
start: number;
|
|
6
|
+
end: number;
|
|
7
|
+
/** Only non-spoken blocks own text here. Paragraph text lives in Scene.script. */
|
|
8
|
+
label?: string;
|
|
9
|
+
level?: number;
|
|
10
|
+
marks: Array<{
|
|
11
|
+
start: number;
|
|
12
|
+
end: number;
|
|
13
|
+
type: 'strong' | 'em';
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
export interface Scene {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
script: string;
|
|
20
|
+
blocks: Block[];
|
|
21
|
+
}
|
|
22
|
+
export interface Shot {
|
|
23
|
+
id: string;
|
|
24
|
+
sceneId: string;
|
|
25
|
+
range: [number, number] | null;
|
|
26
|
+
label: string;
|
|
27
|
+
prompt: string;
|
|
28
|
+
speaker: string;
|
|
29
|
+
}
|
|
30
|
+
export interface Document {
|
|
31
|
+
storyboardId: string;
|
|
32
|
+
recovered?: boolean;
|
|
33
|
+
version: 1;
|
|
34
|
+
revision: number;
|
|
35
|
+
title: string;
|
|
36
|
+
scenes: Scene[];
|
|
37
|
+
shots: Shot[];
|
|
38
|
+
}
|
|
39
|
+
export interface DocumentEdit {
|
|
40
|
+
sceneId: string;
|
|
41
|
+
at: number;
|
|
42
|
+
removed: number;
|
|
43
|
+
text: string;
|
|
44
|
+
}
|
|
45
|
+
export interface DocumentWrite {
|
|
46
|
+
expectedRevision: number;
|
|
47
|
+
edits: DocumentEdit[];
|
|
48
|
+
structures?: Array<{
|
|
49
|
+
sceneId: string;
|
|
50
|
+
blocks: Block[];
|
|
51
|
+
}>;
|
|
52
|
+
move?: {
|
|
53
|
+
sceneId: string;
|
|
54
|
+
blockId: string;
|
|
55
|
+
delta: -1 | 1;
|
|
56
|
+
};
|
|
57
|
+
makeShots?: Array<SectionFragment & {
|
|
58
|
+
waitingShotId?: string;
|
|
59
|
+
}>;
|
|
60
|
+
restoreRevision?: number;
|
|
61
|
+
sceneAction?: {
|
|
62
|
+
kind: 'split';
|
|
63
|
+
sceneId: string;
|
|
64
|
+
at: number;
|
|
65
|
+
} | {
|
|
66
|
+
kind: 'merge';
|
|
67
|
+
sceneId: string;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export interface SectionFragment {
|
|
71
|
+
sceneId: string;
|
|
72
|
+
start: number;
|
|
73
|
+
end: number;
|
|
74
|
+
}
|
|
75
|
+
export interface Section {
|
|
76
|
+
id: string;
|
|
77
|
+
storyboardId: string;
|
|
78
|
+
parentId: string | null;
|
|
79
|
+
label: string;
|
|
80
|
+
tags: string[];
|
|
81
|
+
fragments: SectionFragment[];
|
|
82
|
+
activeAlternativeId: string | null;
|
|
83
|
+
locallyChanged: boolean;
|
|
84
|
+
sourceSectionId?: string | null;
|
|
85
|
+
sourceRevisionId?: string | null;
|
|
86
|
+
alternatives: Array<{
|
|
87
|
+
id: string;
|
|
88
|
+
label: string;
|
|
89
|
+
revisionId: string;
|
|
90
|
+
}>;
|
|
91
|
+
}
|
|
92
|
+
/** `rename` and `archive` act on the section, or on one alternative when
|
|
93
|
+
* alternativeId is given. Archiving a section keeps its words on the page. */
|
|
94
|
+
export interface SectionInput {
|
|
95
|
+
expectedRevision: number;
|
|
96
|
+
action: 'create' | 'alternative' | 'choose' | 'save' | 'reuse' | 'updateUses' | 'rename' | 'archive' | 'tags';
|
|
97
|
+
sectionId?: string;
|
|
98
|
+
alternativeId?: string;
|
|
99
|
+
parentId?: string;
|
|
100
|
+
label?: string;
|
|
101
|
+
tags?: string[];
|
|
102
|
+
fragments?: SectionFragment[];
|
|
103
|
+
target?: {
|
|
104
|
+
sceneId: string;
|
|
105
|
+
at: number;
|
|
106
|
+
expectedRevision: number;
|
|
107
|
+
};
|
|
108
|
+
uses?: Array<{
|
|
109
|
+
sectionId: string;
|
|
110
|
+
expectedRevision: number;
|
|
111
|
+
}>;
|
|
112
|
+
}
|
|
113
|
+
/** A proposed replacement for exact words. It moves with edits around it and
|
|
114
|
+
* applies only while those words are unchanged (`stale` says they are not).
|
|
115
|
+
* Suggesting never edits the document; accepting is one undoable write. */
|
|
116
|
+
export interface ScriptSuggestion {
|
|
117
|
+
id: string;
|
|
118
|
+
storyboardId: string;
|
|
119
|
+
sceneId: string;
|
|
120
|
+
start: number;
|
|
121
|
+
end: number;
|
|
122
|
+
original: string;
|
|
123
|
+
replacement: string;
|
|
124
|
+
note: string;
|
|
125
|
+
status: 'pending' | 'accepted' | 'dismissed';
|
|
126
|
+
stale: boolean;
|
|
127
|
+
baseRevision: number;
|
|
128
|
+
}
|
|
129
|
+
export interface SuggestionInput {
|
|
130
|
+
expectedRevision: number;
|
|
131
|
+
action: 'create' | 'accept' | 'dismiss';
|
|
132
|
+
suggestions?: Array<{
|
|
133
|
+
sceneId: string;
|
|
134
|
+
start: number;
|
|
135
|
+
end: number;
|
|
136
|
+
original: string;
|
|
137
|
+
replacement: string;
|
|
138
|
+
note?: string;
|
|
139
|
+
}>;
|
|
140
|
+
suggestionId?: string;
|
|
141
|
+
}
|
|
142
|
+
export interface VariationChoice {
|
|
143
|
+
sectionId: string;
|
|
144
|
+
alternativeId: string;
|
|
145
|
+
}
|
|
146
|
+
/** Enumerate only on demand; even a large Cartesian set costs one row of memory. */
|
|
147
|
+
export declare function variationCombinations(axes: VariationChoice[][], prefix?: VariationChoice[]): Generator<VariationChoice[]>;
|
|
148
|
+
export interface VariationInput {
|
|
149
|
+
expectedRevision: number;
|
|
150
|
+
name: string;
|
|
151
|
+
choices: VariationChoice[];
|
|
152
|
+
/** When present, this sequence replaces the arrangement: omission and repetition are deliberate. */
|
|
153
|
+
arrangement?: VariationChoice[];
|
|
154
|
+
itemOverrides?: Array<{
|
|
155
|
+
from: string;
|
|
156
|
+
to: string;
|
|
157
|
+
voice: 'keep' | 'replace';
|
|
158
|
+
}>;
|
|
159
|
+
assetOverrides?: Array<{
|
|
160
|
+
from: string;
|
|
161
|
+
to: string;
|
|
162
|
+
}>;
|
|
163
|
+
idempotencyKey?: string;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=script-document.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Enumerate only on demand; even a large Cartesian set costs one row of memory. */
|
|
2
|
+
export function* variationCombinations(axes, prefix = []) {
|
|
3
|
+
if (!axes.length) {
|
|
4
|
+
yield prefix;
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
const [head, ...tail] = axes;
|
|
8
|
+
for (const choice of head)
|
|
9
|
+
yield* variationCombinations(tail, [...prefix, choice]);
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=script-document.js.map
|
|
@@ -87,7 +87,7 @@ export interface SpeechRate {
|
|
|
87
87
|
*/
|
|
88
88
|
export declare const SPEECH_RATE_QUERY: string;
|
|
89
89
|
/** When the numbers below were last derived from the query above. */
|
|
90
|
-
export declare const SPEECH_RATE_MEASURED_ON = "2026-09-
|
|
90
|
+
export declare const SPEECH_RATE_MEASURED_ON = "2026-09-30";
|
|
91
91
|
/**
|
|
92
92
|
* 🔑 THE REGISTER SPLIT IS REAL AND MEASURED, which is why there is no single
|
|
93
93
|
* number. the performed, genre-acted reads run a median of 133 wpm;
|
|
@@ -102,8 +102,8 @@ export declare const SPEECH_RATE: {
|
|
|
102
102
|
readonly segment: "speech_rate = 'performed'";
|
|
103
103
|
};
|
|
104
104
|
readonly conversational: {
|
|
105
|
-
readonly wpm:
|
|
106
|
-
readonly n:
|
|
105
|
+
readonly wpm: 153;
|
|
106
|
+
readonly n: 43;
|
|
107
107
|
readonly segment: "speech_rate = 'conversational'";
|
|
108
108
|
};
|
|
109
109
|
readonly direct_response: {
|
|
@@ -119,7 +119,7 @@ export declare const SPEECH_RATE: {
|
|
|
119
119
|
*/
|
|
120
120
|
readonly ceiling: {
|
|
121
121
|
readonly wpm: 283;
|
|
122
|
-
readonly n:
|
|
122
|
+
readonly n: 67;
|
|
123
123
|
readonly segment: "max over every marked row";
|
|
124
124
|
};
|
|
125
125
|
};
|
|
@@ -124,7 +124,7 @@ SELECT id, speech_rate, duration_seconds, transcript
|
|
|
124
124
|
-- statistic: median per register; ceiling = max over every marked row
|
|
125
125
|
`.trim();
|
|
126
126
|
/** When the numbers below were last derived from the query above. */
|
|
127
|
-
export const SPEECH_RATE_MEASURED_ON = '2026-09-
|
|
127
|
+
export const SPEECH_RATE_MEASURED_ON = '2026-09-30';
|
|
128
128
|
/**
|
|
129
129
|
* 🔑 THE REGISTER SPLIT IS REAL AND MEASURED, which is why there is no single
|
|
130
130
|
* number. the performed, genre-acted reads run a median of 133 wpm;
|
|
@@ -134,7 +134,7 @@ export const SPEECH_RATE_MEASURED_ON = '2026-09-04';
|
|
|
134
134
|
*/
|
|
135
135
|
export const SPEECH_RATE = {
|
|
136
136
|
performed: { wpm: 133, n: 4, segment: "speech_rate = 'performed'" },
|
|
137
|
-
conversational: { wpm:
|
|
137
|
+
conversational: { wpm: 153, n: 43, segment: "speech_rate = 'conversational'" },
|
|
138
138
|
direct_response: { wpm: 169, n: 20, segment: "speech_rate = 'direct_response'" },
|
|
139
139
|
/**
|
|
140
140
|
* The fastest read in the whole corpus. **The fit check flags only ABOVE
|
|
@@ -142,7 +142,7 @@ export const SPEECH_RATE = {
|
|
|
142
142
|
* 250, not p90: those are rates real ads actually hit, and flagging an
|
|
143
143
|
* achievable read is exactly how a check gets ignored.
|
|
144
144
|
*/
|
|
145
|
-
ceiling: { wpm: 283, n:
|
|
145
|
+
ceiling: { wpm: 283, n: 67, segment: 'max over every marked row' },
|
|
146
146
|
};
|
|
147
147
|
/**
|
|
148
148
|
* The default register, and there is deliberately no picker for it.
|
|
@@ -122,6 +122,9 @@ export interface ShotSpec {
|
|
|
122
122
|
* only thing that may number anything, and a stored "image 3" would be a
|
|
123
123
|
* lie the moment a reference is added, removed or reordered. */
|
|
124
124
|
prompt: string;
|
|
125
|
+
/** Custom bytes remain recoverable when script mode is selected. */
|
|
126
|
+
recipeMode?: 'script' | 'custom';
|
|
127
|
+
promptScriptLine?: string | null;
|
|
125
128
|
model: string | null;
|
|
126
129
|
/**
|
|
127
130
|
* The model the PROMPT WAS AUTHORED FOR. Never auto-rewritten.
|
|
@@ -229,6 +232,16 @@ export type ScriptTextField = (typeof SCRIPT_TEXT_FIELDS)[number];
|
|
|
229
232
|
* Dialogue is quoted so a model receives it as speech rather than as
|
|
230
233
|
* description — the one piece of grammar this adds, and the reason it is not
|
|
231
234
|
* just `join(' ')`.
|
|
235
|
+
*
|
|
236
|
+
* 🚨 A LINE IS SPEECH ONLY WHEN THE SHOT SAYS WHO SPEAKS OR HOW (2026-09-27).
|
|
237
|
+
* Since the Script page became one continuous text (2026-09-19, "cues stay
|
|
238
|
+
* words"), `line` holds whatever script text a Shot's range covers, and most
|
|
239
|
+
* of a script is action. Composing every speaker-less line as `A voice says
|
|
240
|
+
* "…"` told the video model that someone speaks the stage direction: an ad
|
|
241
|
+
* script broken into four Shots sent four of them (first-hour walk finding 15).
|
|
242
|
+
* Unlabelled text now goes in as written; a speaker (VO included) or a
|
|
243
|
+
* delivery note still makes it quoted speech. Mirrored byte-for-byte in both
|
|
244
|
+
* repos, and `check:composer-mirror` compares the two outputs.
|
|
232
245
|
*/
|
|
233
246
|
export declare function scriptPromptBody(spec: Pick<ShotSpec, ScriptTextField>): string;
|
|
234
247
|
/**
|
|
@@ -68,6 +68,16 @@ export const SCRIPT_TEXT_FIELDS = [
|
|
|
68
68
|
* Dialogue is quoted so a model receives it as speech rather than as
|
|
69
69
|
* description — the one piece of grammar this adds, and the reason it is not
|
|
70
70
|
* just `join(' ')`.
|
|
71
|
+
*
|
|
72
|
+
* 🚨 A LINE IS SPEECH ONLY WHEN THE SHOT SAYS WHO SPEAKS OR HOW (2026-09-27).
|
|
73
|
+
* Since the Script page became one continuous text (2026-09-19, "cues stay
|
|
74
|
+
* words"), `line` holds whatever script text a Shot's range covers, and most
|
|
75
|
+
* of a script is action. Composing every speaker-less line as `A voice says
|
|
76
|
+
* "…"` told the video model that someone speaks the stage direction: an ad
|
|
77
|
+
* script broken into four Shots sent four of them (first-hour walk finding 15).
|
|
78
|
+
* Unlabelled text now goes in as written; a speaker (VO included) or a
|
|
79
|
+
* delivery note still makes it quoted speech. Mirrored byte-for-byte in both
|
|
80
|
+
* repos, and `check:composer-mirror` compares the two outputs.
|
|
71
81
|
*/
|
|
72
82
|
export function scriptPromptBody(spec) {
|
|
73
83
|
const clean = (v) => (v ?? '').trim();
|
|
@@ -87,10 +97,16 @@ export function scriptPromptBody(spec) {
|
|
|
87
97
|
if (line) {
|
|
88
98
|
const speaker = clean(spec.speaker);
|
|
89
99
|
const delivery = clean(spec.delivery).replace(/^\(|\)$/g, '').trim();
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
100
|
+
if (!speaker && !delivery) {
|
|
101
|
+
// Script text with no one named to speak it: sent as written.
|
|
102
|
+
parts.push(line);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// "VO" is a screenplay abbreviation, not something to send to a model.
|
|
106
|
+
const who = !speaker || speaker.toUpperCase() === 'VO' ? 'A voice' : speaker;
|
|
107
|
+
const how = delivery ? `, ${delivery},` : '';
|
|
108
|
+
parts.push(`${who} says${how} "${line}"`);
|
|
109
|
+
}
|
|
94
110
|
}
|
|
95
111
|
return parts.join(' ');
|
|
96
112
|
}
|
|
@@ -101,7 +117,7 @@ export function scriptPromptBody(spec) {
|
|
|
101
117
|
* while the row plainly has a script in it.
|
|
102
118
|
*/
|
|
103
119
|
export function effectivePrompt(spec) {
|
|
104
|
-
const authored = spec.prompt.trim();
|
|
120
|
+
const authored = spec.recipeMode === 'script' ? '' : spec.prompt.trim();
|
|
105
121
|
return authored || scriptPromptBody(spec);
|
|
106
122
|
}
|
|
107
123
|
/** An empty Shot: a prompt bar nobody has touched. Every reader starts here and
|
|
@@ -247,6 +263,8 @@ export function normalizeShotSpec(raw) {
|
|
|
247
263
|
}
|
|
248
264
|
const out = {
|
|
249
265
|
prompt: typeof v.prompt === 'string' ? v.prompt : '',
|
|
266
|
+
...(v.recipeMode === 'script' || v.recipeMode === 'custom' ? { recipeMode: v.recipeMode } : {}),
|
|
267
|
+
...(typeof v.promptScriptLine === 'string' || v.promptScriptLine === null ? { promptScriptLine: v.promptScriptLine } : {}),
|
|
250
268
|
model: str(v.model),
|
|
251
269
|
authoredFor: str(v.authoredFor),
|
|
252
270
|
params: readParams(v.params),
|