vidspotai-shared 1.0.95 → 1.0.96
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/lib/globals/aiModels/enums.d.ts +2 -0
- package/lib/globals/aiModels/enums.d.ts.map +1 -1
- package/lib/globals/aiModels/enums.js +5 -0
- package/lib/globals/aiModels/index.d.ts.map +1 -1
- package/lib/globals/aiModels/index.js +2 -0
- package/lib/globals/aiModels/providers/hostedAvatars.d.ts +22 -0
- package/lib/globals/aiModels/providers/hostedAvatars.d.ts.map +1 -0
- package/lib/globals/aiModels/providers/hostedAvatars.js +129 -0
- package/lib/globals/types.d.ts +58 -1
- package/lib/globals/types.d.ts.map +1 -1
- package/lib/globals/types.js +61 -1
- package/lib/models/demoVideoProject.model.d.ts +244 -0
- package/lib/models/demoVideoProject.model.d.ts.map +1 -0
- package/lib/models/demoVideoProject.model.js +2 -0
- package/lib/models/githubConnection.model.d.ts +35 -0
- package/lib/models/githubConnection.model.d.ts.map +1 -0
- package/lib/models/githubConnection.model.js +2 -0
- package/lib/models/index.d.ts +3 -0
- package/lib/models/index.d.ts.map +1 -1
- package/lib/models/index.js +3 -0
- package/lib/models/outreach.model.d.ts +175 -0
- package/lib/models/outreach.model.d.ts.map +1 -0
- package/lib/models/outreach.model.js +2 -0
- package/lib/models/video.model.d.ts +4 -0
- package/lib/models/video.model.d.ts.map +1 -1
- package/lib/schemas/brandKit.schema.d.ts +4 -0
- package/lib/schemas/brandKit.schema.d.ts.map +1 -1
- package/lib/schemas/brandKit.schema.js +10 -0
- package/lib/schemas/demoQuestion.schema.d.ts +286 -0
- package/lib/schemas/demoQuestion.schema.d.ts.map +1 -0
- package/lib/schemas/demoQuestion.schema.js +210 -0
- package/lib/schemas/demoSpec.schema.d.ts +109 -6
- package/lib/schemas/demoSpec.schema.d.ts.map +1 -1
- package/lib/schemas/demoSpec.schema.js +95 -7
- package/lib/schemas/index.d.ts +1 -0
- package/lib/schemas/index.d.ts.map +1 -1
- package/lib/schemas/index.js +1 -0
- package/lib/services/agent/providerFallback/chains.d.ts.map +1 -1
- package/lib/services/agent/providerFallback/chains.js +9 -1
- package/lib/services/agent/taskPoller.d.ts.map +1 -1
- package/lib/services/agent/taskPoller.js +17 -6
- package/lib/services/agent/tools/generateAvatarVideo.tool.d.ts +21 -9
- package/lib/services/agent/tools/generateAvatarVideo.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/generateAvatarVideo.tool.js +135 -39
- package/lib/services/aiGen/aiGenFactory.service.d.ts +1 -1
- package/lib/services/aiGen/aiGenFactory.service.d.ts.map +1 -1
- package/lib/services/aiGen/aiGenFactory.service.js +10 -2
- package/lib/services/avatarGen/IAvatarVideoProvider.d.ts +136 -0
- package/lib/services/avatarGen/IAvatarVideoProvider.d.ts.map +1 -0
- package/lib/services/avatarGen/IAvatarVideoProvider.js +42 -0
- package/lib/services/avatarGen/avatarProviderFactory.d.ts +18 -0
- package/lib/services/avatarGen/avatarProviderFactory.d.ts.map +1 -0
- package/lib/services/avatarGen/avatarProviderFactory.js +54 -0
- package/lib/services/avatarGen/index.d.ts +6 -0
- package/lib/services/avatarGen/index.d.ts.map +1 -0
- package/lib/services/avatarGen/index.js +21 -0
- package/lib/services/avatarGen/providers/heygenAvatarProvider.d.ts +45 -0
- package/lib/services/avatarGen/providers/heygenAvatarProvider.d.ts.map +1 -0
- package/lib/services/avatarGen/providers/heygenAvatarProvider.js +190 -0
- package/lib/services/avatarGen/providers/klingAvatarProvider.d.ts +32 -0
- package/lib/services/avatarGen/providers/klingAvatarProvider.d.ts.map +1 -0
- package/lib/services/avatarGen/providers/klingAvatarProvider.js +69 -0
- package/lib/services/avatarGen/providers/tavusAvatarProvider.d.ts +56 -0
- package/lib/services/avatarGen/providers/tavusAvatarProvider.d.ts.map +1 -0
- package/lib/services/avatarGen/providers/tavusAvatarProvider.js +183 -0
- package/lib/services/demo/demoAuthor.d.ts +45 -0
- package/lib/services/demo/demoAuthor.d.ts.map +1 -0
- package/lib/services/demo/demoAuthor.js +75 -0
- package/lib/services/demo/demoAuthorNormalize.d.ts +49 -0
- package/lib/services/demo/demoAuthorNormalize.d.ts.map +1 -0
- package/lib/services/demo/demoAuthorNormalize.js +327 -0
- package/lib/services/demo/demoAuthorPrompt.d.ts +102 -0
- package/lib/services/demo/demoAuthorPrompt.d.ts.map +1 -0
- package/lib/services/demo/demoAuthorPrompt.js +243 -0
- package/lib/services/demo/demoCredits.d.ts +20 -0
- package/lib/services/demo/demoCredits.d.ts.map +1 -0
- package/lib/services/demo/demoCredits.js +51 -0
- package/lib/services/demo/demoMediaGuard.d.ts +43 -0
- package/lib/services/demo/demoMediaGuard.d.ts.map +1 -0
- package/lib/services/demo/demoMediaGuard.js +207 -0
- package/lib/services/demo/demoMusic.d.ts +13 -0
- package/lib/services/demo/demoMusic.d.ts.map +1 -0
- package/lib/services/demo/demoMusic.js +93 -0
- package/lib/services/demo/demoPrefill.d.ts +76 -0
- package/lib/services/demo/demoPrefill.d.ts.map +1 -0
- package/lib/services/demo/demoPrefill.js +435 -0
- package/lib/services/demo/demoRevisePrompt.d.ts +17 -0
- package/lib/services/demo/demoRevisePrompt.d.ts.map +1 -0
- package/lib/services/demo/demoRevisePrompt.js +85 -0
- package/lib/services/demo/demoReviser.d.ts +63 -0
- package/lib/services/demo/demoReviser.d.ts.map +1 -0
- package/lib/services/demo/demoReviser.js +81 -0
- package/lib/services/demo/demoScriptPrompt.d.ts +63 -0
- package/lib/services/demo/demoScriptPrompt.d.ts.map +1 -0
- package/lib/services/demo/demoScriptPrompt.js +234 -0
- package/lib/services/demo/demoScriptWriter.d.ts +65 -0
- package/lib/services/demo/demoScriptWriter.d.ts.map +1 -0
- package/lib/services/demo/demoScriptWriter.js +211 -0
- package/lib/services/demo/demoTriage.d.ts +65 -0
- package/lib/services/demo/demoTriage.d.ts.map +1 -0
- package/lib/services/demo/demoTriage.js +130 -0
- package/lib/services/demo/githubOAuth.d.ts +78 -0
- package/lib/services/demo/githubOAuth.d.ts.map +1 -0
- package/lib/services/demo/githubOAuth.js +209 -0
- package/lib/services/demo/githubOAuthState.d.ts +10 -0
- package/lib/services/demo/githubOAuthState.d.ts.map +1 -0
- package/lib/services/demo/githubOAuthState.js +63 -0
- package/lib/services/demo/index.d.ts +24 -0
- package/lib/services/demo/index.d.ts.map +1 -0
- package/lib/services/demo/index.js +39 -0
- package/lib/services/firestore.service.d.ts +11 -0
- package/lib/services/firestore.service.d.ts.map +1 -1
- package/lib/services/firestore.service.js +24 -0
- package/lib/services/index.d.ts +2 -0
- package/lib/services/index.d.ts.map +1 -1
- package/lib/services/index.js +2 -0
- package/lib/services/tts/providers/elevenlabs.service.d.ts.map +1 -1
- package/lib/services/tts/providers/elevenlabs.service.js +16 -8
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.d.ts.map +1 -1
- package/lib/utils/index.js +1 -0
- package/lib/utils/outreachTemplate.d.ts +46 -0
- package/lib/utils/outreachTemplate.d.ts.map +1 -0
- package/lib/utils/outreachTemplate.js +174 -0
- package/package.json +1 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildDemoReviseSystemPrompt = buildDemoReviseSystemPrompt;
|
|
4
|
+
exports.buildDemoReviseUserText = buildDemoReviseUserText;
|
|
5
|
+
const demoAuthorPrompt_1 = require("./demoAuthorPrompt");
|
|
6
|
+
/**
|
|
7
|
+
* Prompt construction for the "request a change" spec reviser (plan §2.6 result
|
|
8
|
+
* step / Phase 9). The reviser is the structured re-edit path: instead of
|
|
9
|
+
* re-running the wizard from scratch, the user types a natural-language change
|
|
10
|
+
* ("make the intro punchier", "swap scene 3 for a pricing beat", "use a darker
|
|
11
|
+
* mood") and we mutate the EXISTING, already-rendered DemoSpec, then re-render.
|
|
12
|
+
*
|
|
13
|
+
* We reuse the author's system prompt verbatim — it carries the full scene
|
|
14
|
+
* vocabulary, the §2.6 style taxonomy, and the HARD RULES (never invent a
|
|
15
|
+
* screenshot URL, honor brand colors, honor wizard pins). A short REVISION MODE
|
|
16
|
+
* preamble reframes the task from "author from facts" to "apply this change to
|
|
17
|
+
* the given spec", and the user turn carries the current spec as JSON plus the
|
|
18
|
+
* change request. The same deterministic normalize pass then re-enforces every
|
|
19
|
+
* invariant, so a revision can never drift outside the contract the engine
|
|
20
|
+
* renders (real screenshot URLs, brand colors, duration budget, schema-validity).
|
|
21
|
+
*/
|
|
22
|
+
/** Preamble that turns the author prompt into a spec-editing prompt. */
|
|
23
|
+
const REVISION_MODE = `REVISION MODE — you are EDITING an existing DemoSpec, not authoring from scratch.
|
|
24
|
+
The user already has a rendered video from the current spec (provided below as JSON) and is requesting a specific change. Apply ONLY what the change asks for and keep everything else intact:
|
|
25
|
+
- Preserve scene ids, order, copy, and durations that the change does not touch — a stable id lets narration + timing carry over.
|
|
26
|
+
- Make the SMALLEST edit that satisfies the request. Do not re-theme, re-order, or rewrite scenes the user didn't ask you to.
|
|
27
|
+
- If the change is impossible within the scene vocabulary or the HARD RULES (e.g. "use this screenshot" for a URL not in the available list), get as close as the rules allow rather than inventing anything.
|
|
28
|
+
- Keep honoring the LOCKED wizard axes and brand colors exactly as in the current spec.
|
|
29
|
+
- generatedAssets entries carrying status:"ready" + url are ALREADY PAID FOR — keep them (and the scene URLs that came from them) verbatim when their beats survive the change. Declare a NEW generatedAssets entry (cited as "gen://<id>") only when the change genuinely needs media that doesn't exist yet.
|
|
30
|
+
Return the COMPLETE revised DemoSpec via the tool (not a diff) — every scene, including the untouched ones.`;
|
|
31
|
+
/**
|
|
32
|
+
* Build the reviser system prompt: the full author prompt (vocabulary + rules +
|
|
33
|
+
* quality bar) plus the REVISION MODE preamble. `allowCustomScenes` threads the
|
|
34
|
+
* worker's `DEMO_CODEGEN_ENABLED` gate exactly as the author does, so a revision
|
|
35
|
+
* can only introduce a `custom` scene when the codegen agent that fills it is on.
|
|
36
|
+
*/
|
|
37
|
+
function buildDemoReviseSystemPrompt(opts = {}) {
|
|
38
|
+
return `${(0, demoAuthorPrompt_1.buildDemoAuthorSystemPrompt)(opts)}\n\n${REVISION_MODE}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The images the revised spec may cite — same verbatim-only rule as the author.
|
|
42
|
+
* `productUrls` are the project's persisted screenshots; `referenceUrls` are
|
|
43
|
+
* images the user attached to THIS change. Both are citable (copied verbatim onto
|
|
44
|
+
* screenshotUrl/screenshotUrls) and both are attached as vision, but references
|
|
45
|
+
* carry the extra meaning that the user is pointing at them for this edit — so
|
|
46
|
+
* the model should use them as the visual target (match this look/layout, or show
|
|
47
|
+
* this exact UI). If there are no images of either kind, deviceMockup is off.
|
|
48
|
+
*/
|
|
49
|
+
function citableSection(productUrls, referenceUrls) {
|
|
50
|
+
const hasProduct = !!productUrls?.length;
|
|
51
|
+
const hasRef = !!referenceUrls?.length;
|
|
52
|
+
if (!hasProduct && !hasRef) {
|
|
53
|
+
return `Available screenshots: NONE. Do not introduce deviceMockup; author any new product UI as fully synthetic featureShowcase surfaces.`;
|
|
54
|
+
}
|
|
55
|
+
const blocks = [];
|
|
56
|
+
if (hasProduct) {
|
|
57
|
+
const lines = productUrls.map((u, i) => ` [${i}] ${u}`).join("\n");
|
|
58
|
+
blocks.push(`Available product screenshots (${productUrls.length}) — URLs you may use, copied verbatim, on screenshotUrl/screenshotUrls:\n${lines}`);
|
|
59
|
+
}
|
|
60
|
+
if (hasRef) {
|
|
61
|
+
const lines = referenceUrls.map((u, i) => ` [R${i}] ${u}`).join("\n");
|
|
62
|
+
blocks.push(`REFERENCE IMAGES the user attached WITH THIS CHANGE (${referenceUrls.length}) — this is the look/layout/screenshot they are pointing at for the requested edit. Use them as the visual target: match the design, layout, or content they show. You MAY also cite these URLs verbatim on screenshotUrl/screenshotUrls (exactly like product screenshots) when the change asks to SHOW one of them:\n${lines}`);
|
|
63
|
+
}
|
|
64
|
+
blocks.push("All of the above images are attached to this message for you to see.");
|
|
65
|
+
return blocks.join("\n\n");
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build the reviser user turn: the current spec (as JSON) + the enumerated
|
|
69
|
+
* citable images (product screenshots + the user's per-edit reference images) +
|
|
70
|
+
* the user's natural-language change request. Images are attached separately as
|
|
71
|
+
* `imageUrls` (mirrors the author).
|
|
72
|
+
*/
|
|
73
|
+
function buildDemoReviseUserText(spec, instruction, productScreenshotUrls, referenceImageUrls) {
|
|
74
|
+
return [
|
|
75
|
+
"CURRENT DemoSpec (edit this — return the full revised spec):",
|
|
76
|
+
"```json",
|
|
77
|
+
JSON.stringify(spec, null, 2),
|
|
78
|
+
"```",
|
|
79
|
+
"",
|
|
80
|
+
citableSection(productScreenshotUrls, referenceImageUrls),
|
|
81
|
+
"",
|
|
82
|
+
"CHANGE REQUESTED by the user:",
|
|
83
|
+
instruction.trim(),
|
|
84
|
+
].join("\n");
|
|
85
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { DemoSpec } from "../../schemas/demoSpec.schema";
|
|
2
|
+
import { LlmCaller } from "../agent/llmCaller";
|
|
3
|
+
import { ModelChoice, ModelRouter } from "../agent/modelRouter";
|
|
4
|
+
import { DemoAuthorInput } from "./demoAuthorPrompt";
|
|
5
|
+
import { NormalizeResult } from "./demoAuthorNormalize";
|
|
6
|
+
export interface DemoReviseDeps {
|
|
7
|
+
/**
|
|
8
|
+
* Normalize context — the SAME resolved inputs the spec was authored from
|
|
9
|
+
* (product name, resolved brand, the real screenshot URL set, wizard answers).
|
|
10
|
+
* The worker reconstructs this from the persisted project (no re-gather), so
|
|
11
|
+
* the normalize pass re-enforces exactly the invariants the original author
|
|
12
|
+
* had to (screenshot sanitation, brand-color forcing, wizard pins).
|
|
13
|
+
*/
|
|
14
|
+
context: DemoAuthorInput;
|
|
15
|
+
/** LLM caller. Defaults to the host-wired dispatcher. */
|
|
16
|
+
llm?: LlmCaller;
|
|
17
|
+
/** Model router. Defaults to DEFAULT_MODEL_ROUTING. */
|
|
18
|
+
router?: ModelRouter;
|
|
19
|
+
/** Route the reviser to the premium tier (Opus) for higher-fidelity edits. */
|
|
20
|
+
premium?: boolean;
|
|
21
|
+
/** Sampling temperature. Lower than authoring — a revision should be conservative. */
|
|
22
|
+
temperature?: number;
|
|
23
|
+
/** Max output tokens — a full DemoSpec with rich scenes can be large. */
|
|
24
|
+
maxTokens?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Allow the reviser to introduce Layer-B `custom` scenes. Threaded from the
|
|
27
|
+
* worker's `DEMO_CODEGEN_ENABLED` gate; default false so a revision never
|
|
28
|
+
* emits a `custom` scene the pipeline can't fill.
|
|
29
|
+
*/
|
|
30
|
+
allowCustomScenes?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Allow the revision to commission AI-generated media (`generatedAssets` +
|
|
33
|
+
* `gen://` refs). Default true; false only under DEMO_GEN_ASSETS_DISABLED.
|
|
34
|
+
*/
|
|
35
|
+
allowGeneratedAssets?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Reference images the user attached to THIS change (public URLs) — e.g. a
|
|
38
|
+
* screenshot of the look/layout they mean, or a UI they want shown. Distinct
|
|
39
|
+
* from the persisted product screenshots in `context`: they are the user's
|
|
40
|
+
* intent for this one edit, so they get first claim on the vision budget and
|
|
41
|
+
* are added to the citable whitelist (the model may match them or cite them
|
|
42
|
+
* verbatim), but they are NOT persisted into the project's screenshot set.
|
|
43
|
+
*/
|
|
44
|
+
referenceImageUrls?: string[];
|
|
45
|
+
}
|
|
46
|
+
export interface DemoReviseResult extends NormalizeResult {
|
|
47
|
+
/** Model that produced the revised spec (primary or fallback). */
|
|
48
|
+
modelUsed: ModelChoice;
|
|
49
|
+
usedFallback?: boolean;
|
|
50
|
+
usage?: {
|
|
51
|
+
promptTokens?: number;
|
|
52
|
+
completionTokens?: number;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Apply a natural-language change to an existing DemoSpec. Pure orchestration:
|
|
57
|
+
* build the reviser prompt (current spec as JSON + the change), re-attach the
|
|
58
|
+
* screenshots as vision, call `.structured(DemoSpec)`, then normalize. Throws if
|
|
59
|
+
* the caller is unconfigured or the model returns an unparseable / scene-less
|
|
60
|
+
* spec (the normalize pass is the last guard).
|
|
61
|
+
*/
|
|
62
|
+
export declare function reviseDemoSpec(currentSpec: DemoSpec, instruction: string, deps: DemoReviseDeps): Promise<DemoReviseResult>;
|
|
63
|
+
//# sourceMappingURL=demoReviser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demoReviser.d.ts","sourceRoot":"","sources":["../../../src/services/demo/demoReviser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAkB,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAKrD,OAAO,EAAE,eAAe,EAAqB,MAAM,uBAAuB,CAAC;AAoB3E,MAAM,WAAW,cAAc;IAC7B;;;;;;OAMG;IACH,OAAO,EAAE,eAAe,CAAC;IACzB,yDAAyD;IACzD,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,uDAAuD;IACvD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sFAAsF;IACtF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,kEAAkE;IAClE,SAAS,EAAE,WAAW,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,KAAK,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9D;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,QAAQ,EACrB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,gBAAgB,CAAC,CA+D3B"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.reviseDemoSpec = reviseDemoSpec;
|
|
4
|
+
const demoSpec_schema_1 = require("../../schemas/demoSpec.schema");
|
|
5
|
+
const llmCallerRegistry_1 = require("../agent/llmCallerRegistry");
|
|
6
|
+
const modelRouter_1 = require("../agent/modelRouter");
|
|
7
|
+
const demoRevisePrompt_1 = require("./demoRevisePrompt");
|
|
8
|
+
const demoAuthorNormalize_1 = require("./demoAuthorNormalize");
|
|
9
|
+
/**
|
|
10
|
+
* DemoSpec reviser — the "request a change" re-edit path (plan §2.6 / Phase 9).
|
|
11
|
+
*
|
|
12
|
+
* ONE Claude `.structured<DemoSpec>()` call takes the CURRENT (already-rendered)
|
|
13
|
+
* spec + a natural-language change request and returns the full revised spec,
|
|
14
|
+
* which the same deterministic normalize pass re-hardens (real screenshot URLs,
|
|
15
|
+
* brand colors, duration budget, schema-validity). Structurally identical to
|
|
16
|
+
* `authorDemoSpec` — provider-agnostic (injected `LlmCaller`), model-routed —
|
|
17
|
+
* but seeded with the existing spec instead of authoring from raw facts, so a
|
|
18
|
+
* change costs one LLM call + a re-render rather than a wizard restart.
|
|
19
|
+
*
|
|
20
|
+
* The worker calls this at a REVISING stage when `pendingRevision` is set on the
|
|
21
|
+
* project doc, persists the returned spec, and re-renders from it.
|
|
22
|
+
*/
|
|
23
|
+
/** Cap screenshots re-attached as vision (mirrors the author's MAX_VISION_SCREENSHOTS). */
|
|
24
|
+
const MAX_VISION_SCREENSHOTS = 6;
|
|
25
|
+
/**
|
|
26
|
+
* Apply a natural-language change to an existing DemoSpec. Pure orchestration:
|
|
27
|
+
* build the reviser prompt (current spec as JSON + the change), re-attach the
|
|
28
|
+
* screenshots as vision, call `.structured(DemoSpec)`, then normalize. Throws if
|
|
29
|
+
* the caller is unconfigured or the model returns an unparseable / scene-less
|
|
30
|
+
* spec (the normalize pass is the last guard).
|
|
31
|
+
*/
|
|
32
|
+
async function reviseDemoSpec(currentSpec, instruction, deps) {
|
|
33
|
+
const llm = deps.llm ?? (0, llmCallerRegistry_1.getLlmCaller)();
|
|
34
|
+
const router = deps.router ?? new modelRouter_1.ModelRouter();
|
|
35
|
+
const model = router.pickFor("planner", { premium: deps.premium });
|
|
36
|
+
const fallbackModel = router.fallbackFor("planner");
|
|
37
|
+
// Same cap-before-prompt discipline as the author: only advertise (and attach)
|
|
38
|
+
// the images the model can actually see, so the citable set matches. The user's
|
|
39
|
+
// per-edit reference images get FIRST claim on the vision budget (they are the
|
|
40
|
+
// explicit intent for this change); product screenshots fill the remainder.
|
|
41
|
+
const refShots = [...new Set(deps.referenceImageUrls ?? [])].slice(0, MAX_VISION_SCREENSHOTS);
|
|
42
|
+
const productShots = [...new Set(deps.context.screenshotUrls ?? [])]
|
|
43
|
+
.filter((u) => !refShots.includes(u))
|
|
44
|
+
.slice(0, Math.max(0, MAX_VISION_SCREENSHOTS - refShots.length));
|
|
45
|
+
// Everything the model may SEE and CITE this edit (product + references).
|
|
46
|
+
const visionImageUrls = [...productShots, ...refShots];
|
|
47
|
+
const cappedContext = {
|
|
48
|
+
...deps.context,
|
|
49
|
+
screenshotUrls: visionImageUrls.length ? visionImageUrls : undefined,
|
|
50
|
+
};
|
|
51
|
+
const system = (0, demoRevisePrompt_1.buildDemoReviseSystemPrompt)({
|
|
52
|
+
allowCustomScenes: deps.allowCustomScenes,
|
|
53
|
+
allowGeneratedAssets: deps.allowGeneratedAssets,
|
|
54
|
+
});
|
|
55
|
+
const userText = (0, demoRevisePrompt_1.buildDemoReviseUserText)(currentSpec, instruction, productShots, refShots);
|
|
56
|
+
const result = await llm.structured({
|
|
57
|
+
model,
|
|
58
|
+
fallbackModel,
|
|
59
|
+
messages: [
|
|
60
|
+
{ role: "system", content: system },
|
|
61
|
+
{
|
|
62
|
+
role: "user",
|
|
63
|
+
content: userText,
|
|
64
|
+
imageUrls: visionImageUrls.length ? visionImageUrls : undefined,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
schema: demoSpec_schema_1.DemoSpecSchema,
|
|
68
|
+
schemaName: "DemoSpec",
|
|
69
|
+
// Conservative: a revision should change what was asked and little else.
|
|
70
|
+
temperature: deps.temperature ?? 0.3,
|
|
71
|
+
maxTokens: deps.maxTokens ?? 8000,
|
|
72
|
+
});
|
|
73
|
+
const raw = demoSpec_schema_1.DemoSpecSchema.parse(result.data);
|
|
74
|
+
const normalized = (0, demoAuthorNormalize_1.normalizeDemoSpec)(raw, cappedContext);
|
|
75
|
+
return {
|
|
76
|
+
...normalized,
|
|
77
|
+
modelUsed: result.usedFallback && fallbackModel ? fallbackModel : model,
|
|
78
|
+
usedFallback: result.usedFallback,
|
|
79
|
+
usage: result.usage,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { DemoScene, DemoSpec } from "../../schemas/demoSpec.schema";
|
|
2
|
+
/**
|
|
3
|
+
* Prompt construction for the SaaS Demo Video narration writer
|
|
4
|
+
* (plan Phase 5 / §4 stage 4 / §6).
|
|
5
|
+
*
|
|
6
|
+
* Unlike the reference workflow (visual-first → Gemini analyses the recording →
|
|
7
|
+
* script matched to timestamps), WE OWN the timeline: the DemoSpec's scene
|
|
8
|
+
* durations are already exact, so the narration is authored already aligned to
|
|
9
|
+
* scene start/end frames — no recording round-trip. This module turns each scene
|
|
10
|
+
* into a compact brief (id, type, duration, on-screen copy, the author's
|
|
11
|
+
* `narrationHint`) and asks Claude for one concise spoken line per scene, sized
|
|
12
|
+
* to that scene's spoken-time budget so the TTS lands inside the scene window.
|
|
13
|
+
*
|
|
14
|
+
* The prompt only DESCRIBES intent; `demoScriptWriter.ts` enforces the hard
|
|
15
|
+
* guarantees a prompt can only request (valid sceneIds, one segment per scene,
|
|
16
|
+
* timeline offsets) via a deterministic normalize pass.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Natural narration rate — ~150 wpm. Matches `elevenlabsConfig.wordsPerSecond`
|
|
20
|
+
* (the TTS provider's nominal rate) so the word budget we hand the author and the
|
|
21
|
+
* spoken duration the voice stage estimates agree.
|
|
22
|
+
*/
|
|
23
|
+
export declare const SCRIPT_WORDS_PER_SECOND = 2.5;
|
|
24
|
+
/** Spoken-word budget for a scene of `durationMs`, given the fill factor. */
|
|
25
|
+
export declare function scriptWordBudget(durationMs: number): number;
|
|
26
|
+
/** Rough spoken-word count (whitespace tokens). */
|
|
27
|
+
export declare function wordCount(text: string): number;
|
|
28
|
+
/**
|
|
29
|
+
* Deterministically fit narration to a spoken-word `budget` so the synthesized
|
|
30
|
+
* speech lands INSIDE its scene window — instead of overrunning and either
|
|
31
|
+
* overlapping the next scene's voice or being clipped/rushed by the voice stage.
|
|
32
|
+
* This is the hard enforcement behind the prompt's soft "respect the wordBudget"
|
|
33
|
+
* ask (the model has been observed to overshoot ~2×).
|
|
34
|
+
*
|
|
35
|
+
* Strategy, preferring to end on a natural boundary:
|
|
36
|
+
* 1. keep as many WHOLE sentences as fit within budget;
|
|
37
|
+
* 2. if even the first sentence is over budget, cap at `budget` words and back
|
|
38
|
+
* off to the last clause boundary (comma/semicolon/dash) in that span;
|
|
39
|
+
* 3. always end on terminal punctuation.
|
|
40
|
+
* Never lengthens. Returns the (possibly) trimmed text + whether it changed.
|
|
41
|
+
*/
|
|
42
|
+
export declare function fitNarrationToBudget(text: string, budget: number): {
|
|
43
|
+
text: string;
|
|
44
|
+
trimmed: boolean;
|
|
45
|
+
};
|
|
46
|
+
/** A compact, model-facing view of one scene for the narration writer. */
|
|
47
|
+
export interface DemoScriptSceneBrief {
|
|
48
|
+
sceneId: string;
|
|
49
|
+
index: number;
|
|
50
|
+
type: DemoScene["type"];
|
|
51
|
+
durationMs: number;
|
|
52
|
+
/** Suggested spoken-word ceiling so the line fits the scene window. */
|
|
53
|
+
wordBudget: number;
|
|
54
|
+
/** Salient on-screen copy the narration should complement (not read verbatim). */
|
|
55
|
+
onScreen: string;
|
|
56
|
+
/** The author's optional per-scene narration intent. */
|
|
57
|
+
narrationHint?: string;
|
|
58
|
+
}
|
|
59
|
+
/** Build the per-scene briefs the writer reasons over, in timeline order. */
|
|
60
|
+
export declare function buildDemoScriptBriefs(spec: DemoSpec): DemoScriptSceneBrief[];
|
|
61
|
+
export declare function buildDemoScriptSystemPrompt(): string;
|
|
62
|
+
export declare function buildDemoScriptUserText(spec: DemoSpec, briefs: DemoScriptSceneBrief[]): string;
|
|
63
|
+
//# sourceMappingURL=demoScriptPrompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demoScriptPrompt.d.ts","sourceRoot":"","sources":["../../../src/services/demo/demoScriptPrompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAEpE;;;;;;;;;;;;;;;GAeG;AAEH;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAY3C,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAM3D;AAED,mDAAmD;AACnD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AA4BD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CA6BpC;AAED,0EAA0E;AAC1E,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,QAAQ,EAAE,MAAM,CAAC;IACjB,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAuED,6EAA6E;AAC7E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,GAAG,oBAAoB,EAAE,CAU5E;AAED,wBAAgB,2BAA2B,IAAI,MAAM,CAkBpD;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,oBAAoB,EAAE,GAC7B,MAAM,CAuBR"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SCRIPT_WORDS_PER_SECOND = void 0;
|
|
4
|
+
exports.scriptWordBudget = scriptWordBudget;
|
|
5
|
+
exports.wordCount = wordCount;
|
|
6
|
+
exports.fitNarrationToBudget = fitNarrationToBudget;
|
|
7
|
+
exports.buildDemoScriptBriefs = buildDemoScriptBriefs;
|
|
8
|
+
exports.buildDemoScriptSystemPrompt = buildDemoScriptSystemPrompt;
|
|
9
|
+
exports.buildDemoScriptUserText = buildDemoScriptUserText;
|
|
10
|
+
/**
|
|
11
|
+
* Prompt construction for the SaaS Demo Video narration writer
|
|
12
|
+
* (plan Phase 5 / §4 stage 4 / §6).
|
|
13
|
+
*
|
|
14
|
+
* Unlike the reference workflow (visual-first → Gemini analyses the recording →
|
|
15
|
+
* script matched to timestamps), WE OWN the timeline: the DemoSpec's scene
|
|
16
|
+
* durations are already exact, so the narration is authored already aligned to
|
|
17
|
+
* scene start/end frames — no recording round-trip. This module turns each scene
|
|
18
|
+
* into a compact brief (id, type, duration, on-screen copy, the author's
|
|
19
|
+
* `narrationHint`) and asks Claude for one concise spoken line per scene, sized
|
|
20
|
+
* to that scene's spoken-time budget so the TTS lands inside the scene window.
|
|
21
|
+
*
|
|
22
|
+
* The prompt only DESCRIBES intent; `demoScriptWriter.ts` enforces the hard
|
|
23
|
+
* guarantees a prompt can only request (valid sceneIds, one segment per scene,
|
|
24
|
+
* timeline offsets) via a deterministic normalize pass.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Natural narration rate — ~150 wpm. Matches `elevenlabsConfig.wordsPerSecond`
|
|
28
|
+
* (the TTS provider's nominal rate) so the word budget we hand the author and the
|
|
29
|
+
* spoken duration the voice stage estimates agree.
|
|
30
|
+
*/
|
|
31
|
+
exports.SCRIPT_WORDS_PER_SECOND = 2.5;
|
|
32
|
+
/**
|
|
33
|
+
* Fraction of a scene we let narration fill. < 1 leaves breathing room (natural
|
|
34
|
+
* pauses, and short logo/CTA beats that shouldn't be wall-to-wall speech). The
|
|
35
|
+
* voice stage applies a mild speed-up (≤1.2×) for any segment that still overruns.
|
|
36
|
+
*/
|
|
37
|
+
const SCRIPT_FILL_FACTOR = 0.85;
|
|
38
|
+
/** Never ask for fewer than this — a 1s beat still gets a 2–3 word line. */
|
|
39
|
+
const MIN_SCRIPT_WORDS = 2;
|
|
40
|
+
/** Spoken-word budget for a scene of `durationMs`, given the fill factor. */
|
|
41
|
+
function scriptWordBudget(durationMs) {
|
|
42
|
+
const seconds = durationMs / 1000;
|
|
43
|
+
return Math.max(MIN_SCRIPT_WORDS, Math.round(seconds * exports.SCRIPT_WORDS_PER_SECOND * SCRIPT_FILL_FACTOR));
|
|
44
|
+
}
|
|
45
|
+
/** Rough spoken-word count (whitespace tokens). */
|
|
46
|
+
function wordCount(text) {
|
|
47
|
+
return text.trim().split(/\s+/).filter(Boolean).length;
|
|
48
|
+
}
|
|
49
|
+
function ensureTerminalPunctuation(s) {
|
|
50
|
+
const trimmed = s.replace(/[\s,;:—–-]+$/, "").trim();
|
|
51
|
+
return /[.!?]$/.test(trimmed) ? trimmed : `${trimmed}.`;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Split into sentences on REAL boundaries only: a `.`/`!`/`?` that is followed by
|
|
55
|
+
* whitespace + an uppercase letter, or by end-of-string. This deliberately does
|
|
56
|
+
* NOT break on the `.` inside decimals ("4.9") or domains ("vidspotai.com"), which
|
|
57
|
+
* a naive `/[.!?]/` split mangles.
|
|
58
|
+
*/
|
|
59
|
+
function splitSentences(text) {
|
|
60
|
+
const out = [];
|
|
61
|
+
const re = /[.!?]+(?=\s+\p{Lu}|\s*$)/gu;
|
|
62
|
+
let last = 0;
|
|
63
|
+
let m;
|
|
64
|
+
while ((m = re.exec(text)) !== null) {
|
|
65
|
+
const end = m.index + m[0].length;
|
|
66
|
+
out.push(text.slice(last, end).trim());
|
|
67
|
+
last = end;
|
|
68
|
+
if (re.lastIndex === m.index)
|
|
69
|
+
re.lastIndex++; // guard against zero-width loops
|
|
70
|
+
}
|
|
71
|
+
if (last < text.length)
|
|
72
|
+
out.push(text.slice(last).trim());
|
|
73
|
+
return out.filter(Boolean);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Deterministically fit narration to a spoken-word `budget` so the synthesized
|
|
77
|
+
* speech lands INSIDE its scene window — instead of overrunning and either
|
|
78
|
+
* overlapping the next scene's voice or being clipped/rushed by the voice stage.
|
|
79
|
+
* This is the hard enforcement behind the prompt's soft "respect the wordBudget"
|
|
80
|
+
* ask (the model has been observed to overshoot ~2×).
|
|
81
|
+
*
|
|
82
|
+
* Strategy, preferring to end on a natural boundary:
|
|
83
|
+
* 1. keep as many WHOLE sentences as fit within budget;
|
|
84
|
+
* 2. if even the first sentence is over budget, cap at `budget` words and back
|
|
85
|
+
* off to the last clause boundary (comma/semicolon/dash) in that span;
|
|
86
|
+
* 3. always end on terminal punctuation.
|
|
87
|
+
* Never lengthens. Returns the (possibly) trimmed text + whether it changed.
|
|
88
|
+
*/
|
|
89
|
+
function fitNarrationToBudget(text, budget) {
|
|
90
|
+
const clean = text.trim().replace(/\s+/g, " ");
|
|
91
|
+
if (!clean)
|
|
92
|
+
return { text: clean, trimmed: false };
|
|
93
|
+
if (wordCount(clean) <= budget)
|
|
94
|
+
return { text: clean, trimmed: false };
|
|
95
|
+
// 1) whole sentences up to budget
|
|
96
|
+
const sentences = splitSentences(clean);
|
|
97
|
+
let acc = "";
|
|
98
|
+
for (const s of sentences) {
|
|
99
|
+
const candidate = acc ? `${acc} ${s}` : s;
|
|
100
|
+
if (wordCount(candidate) > budget)
|
|
101
|
+
break;
|
|
102
|
+
acc = candidate;
|
|
103
|
+
}
|
|
104
|
+
if (acc)
|
|
105
|
+
return { text: ensureTerminalPunctuation(acc), trimmed: true };
|
|
106
|
+
// 2) first sentence already over budget → cap at budget words, back off to the
|
|
107
|
+
// last clause boundary in the capped span (only if it keeps ≥ half the budget)
|
|
108
|
+
const capped = clean.split(" ").slice(0, Math.max(1, budget)).join(" ");
|
|
109
|
+
const clauseIdx = Math.max(capped.lastIndexOf(","), capped.lastIndexOf(";"), capped.lastIndexOf("—"), capped.lastIndexOf("–"));
|
|
110
|
+
const out = clauseIdx > 0 && wordCount(capped.slice(0, clauseIdx)) >= Math.ceil(budget * 0.5)
|
|
111
|
+
? capped.slice(0, clauseIdx)
|
|
112
|
+
: capped;
|
|
113
|
+
return { text: ensureTerminalPunctuation(out), trimmed: true };
|
|
114
|
+
}
|
|
115
|
+
/** Pull the salient on-screen copy from a scene so narration can complement it. */
|
|
116
|
+
function sceneCopyDigest(scene) {
|
|
117
|
+
const parts = [];
|
|
118
|
+
const push = (v) => {
|
|
119
|
+
if (v && v.trim())
|
|
120
|
+
parts.push(v.trim());
|
|
121
|
+
};
|
|
122
|
+
if (scene.caption) {
|
|
123
|
+
push(scene.caption.title);
|
|
124
|
+
push(scene.caption.sub);
|
|
125
|
+
}
|
|
126
|
+
switch (scene.type) {
|
|
127
|
+
case "problem":
|
|
128
|
+
push(scene.headline);
|
|
129
|
+
break;
|
|
130
|
+
case "logoReveal":
|
|
131
|
+
push(scene.tagline);
|
|
132
|
+
break;
|
|
133
|
+
case "featureShowcase":
|
|
134
|
+
push(scene.title);
|
|
135
|
+
push(scene.cursorTypes);
|
|
136
|
+
scene.cards.forEach((c) => push(c.title));
|
|
137
|
+
break;
|
|
138
|
+
case "splitFeature":
|
|
139
|
+
push(scene.stepLabel);
|
|
140
|
+
push(scene.headline);
|
|
141
|
+
push(scene.body);
|
|
142
|
+
break;
|
|
143
|
+
case "bigStatement":
|
|
144
|
+
push(scene.kicker);
|
|
145
|
+
scene.lines.forEach((l) => push(l.replace(/\*\*/g, "")));
|
|
146
|
+
break;
|
|
147
|
+
case "deviceMockup":
|
|
148
|
+
push(scene.headline);
|
|
149
|
+
break;
|
|
150
|
+
case "mediaShot":
|
|
151
|
+
push(scene.headline?.replace(/\*\*/g, ""));
|
|
152
|
+
break;
|
|
153
|
+
case "chat":
|
|
154
|
+
push(scene.title);
|
|
155
|
+
scene.turns.forEach((t) => push(`${t.role}: ${t.text}`));
|
|
156
|
+
break;
|
|
157
|
+
case "dashboard":
|
|
158
|
+
push(scene.title);
|
|
159
|
+
scene.stats.forEach((s) => push(s.label));
|
|
160
|
+
break;
|
|
161
|
+
case "docAnalysis":
|
|
162
|
+
push(scene.title);
|
|
163
|
+
scene.insights.forEach((i) => push(i.title));
|
|
164
|
+
break;
|
|
165
|
+
case "cardGrid":
|
|
166
|
+
push(scene.title);
|
|
167
|
+
scene.cards.forEach((c) => push(c.title));
|
|
168
|
+
break;
|
|
169
|
+
case "stats":
|
|
170
|
+
push(scene.headline);
|
|
171
|
+
scene.stats.forEach((s) => push(`${s.valueText ?? s.value ?? ""} ${s.label}`.trim()));
|
|
172
|
+
break;
|
|
173
|
+
case "cta":
|
|
174
|
+
push(scene.headline);
|
|
175
|
+
push(scene.buttonText);
|
|
176
|
+
break;
|
|
177
|
+
case "custom":
|
|
178
|
+
// Path B (Phase 8) — no structured copy to digest.
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
// Keep the digest bounded — long chat/card lists would bloat the prompt.
|
|
182
|
+
return parts.slice(0, 8).join(" · ").slice(0, 400);
|
|
183
|
+
}
|
|
184
|
+
/** Build the per-scene briefs the writer reasons over, in timeline order. */
|
|
185
|
+
function buildDemoScriptBriefs(spec) {
|
|
186
|
+
return spec.scenes.map((scene, index) => ({
|
|
187
|
+
sceneId: scene.id,
|
|
188
|
+
index,
|
|
189
|
+
type: scene.type,
|
|
190
|
+
durationMs: scene.durationMs,
|
|
191
|
+
wordBudget: scriptWordBudget(scene.durationMs),
|
|
192
|
+
onScreen: sceneCopyDigest(scene),
|
|
193
|
+
...(scene.narrationHint ? { narrationHint: scene.narrationHint } : {}),
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
function buildDemoScriptSystemPrompt() {
|
|
197
|
+
return [
|
|
198
|
+
"You are the narration writer for an autonomous SaaS product-demo video.",
|
|
199
|
+
"The video's scenes and exact durations are ALREADY fixed — you write the voiceover that plays over them.",
|
|
200
|
+
"",
|
|
201
|
+
"Write a single, natural spoken line for each scene that deserves narration.",
|
|
202
|
+
"Rules:",
|
|
203
|
+
'- One segment per scene, addressed by its exact `sceneId` — the quoted value shown as sceneId=\"…\". Copy it verbatim. It is NOT the scene type in parentheses; never return the type, and never invent a sceneId.',
|
|
204
|
+
"- You MAY omit a scene (return no segment for it) when narration would be redundant — e.g. a bare logo sting or a wordless transition. Prefer omission over filler.",
|
|
205
|
+
"- HARD LIMIT: never exceed a scene's `wordBudget`. It is the number of words that actually FIT in that scene's spoken time. A line over budget is deterministically TRIMMED to fit (your later words are dropped), and overrunning also causes the voice to overlap the next scene. So lead with the essential point and stay at or below the budget — write SHORTER, not longer.",
|
|
206
|
+
"- COMPLEMENT the on-screen copy — don't read captions/headlines back verbatim. Add the value, the 'why it matters', the connective tissue.",
|
|
207
|
+
"- Natural, confident, conversational brand voice. No hype clichés ('revolutionary', 'game-changing'), no reading of UI chrome, no stage directions, no emoji.",
|
|
208
|
+
"- One continuous through-line across scenes: problem → product → payoff → call to action. Each line should follow from the last when read in order.",
|
|
209
|
+
"- Plain spoken sentences only (this is fed to text-to-speech). No markdown, no bullet points, no SSML.",
|
|
210
|
+
"- Match the audience and narrative tone provided.",
|
|
211
|
+
"",
|
|
212
|
+
"Return segments in timeline order, each { sceneId, text }. `text` is exactly what will be spoken (empty/omit to skip a scene).",
|
|
213
|
+
].join("\n");
|
|
214
|
+
}
|
|
215
|
+
function buildDemoScriptUserText(spec, briefs) {
|
|
216
|
+
const totalSec = Math.round(briefs.reduce((acc, b) => acc + b.durationMs, 0) / 1000);
|
|
217
|
+
const lines = [
|
|
218
|
+
`Product: ${spec.productName}`,
|
|
219
|
+
...(spec.tagline ? [`Tagline: ${spec.tagline}`] : []),
|
|
220
|
+
...(spec.audience ? [`Audience: ${spec.audience}`] : []),
|
|
221
|
+
`Narrative arc: ${spec.narrative}`,
|
|
222
|
+
`Audio intent: ${spec.style.audioIntent} (captions = sparse VO, on-screen text leads · voiceover = VO leads, carry the story · music = keep VO minimal or omit)`,
|
|
223
|
+
`Total runtime: ~${totalSec}s across ${briefs.length} scenes.`,
|
|
224
|
+
"",
|
|
225
|
+
"Scenes (timeline order):",
|
|
226
|
+
];
|
|
227
|
+
for (const b of briefs) {
|
|
228
|
+
const secs = (b.durationMs / 1000).toFixed(1);
|
|
229
|
+
const hint = b.narrationHint ? ` | hint: ${b.narrationHint}` : "";
|
|
230
|
+
const onScreen = b.onScreen ? ` | on-screen: ${b.onScreen}` : "";
|
|
231
|
+
lines.push(`- sceneId="${b.sceneId}" (${b.type} scene, ${secs}s, ≤${b.wordBudget} words)${onScreen}${hint}`);
|
|
232
|
+
}
|
|
233
|
+
return lines.join("\n");
|
|
234
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { DemoSpec } from "../../schemas/demoSpec.schema";
|
|
2
|
+
import { IDemoVideoScriptSegment } from "../../models/demoVideoProject.model";
|
|
3
|
+
import { LlmCaller } from "../agent/llmCaller";
|
|
4
|
+
import { ModelChoice, ModelRouter } from "../agent/modelRouter";
|
|
5
|
+
export interface DemoScriptDeps {
|
|
6
|
+
/** LLM caller. Defaults to the host-wired dispatcher. */
|
|
7
|
+
llm?: LlmCaller;
|
|
8
|
+
/** Model router. Defaults to DEFAULT_MODEL_ROUTING. */
|
|
9
|
+
router?: ModelRouter;
|
|
10
|
+
/** Route to the premium tier (Opus) for higher-fidelity copy. */
|
|
11
|
+
premium?: boolean;
|
|
12
|
+
/** Sampling temperature. Slightly creative but on-brand. */
|
|
13
|
+
temperature?: number;
|
|
14
|
+
/** Max output tokens — a script is small (a few lines per scene). */
|
|
15
|
+
maxTokens?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface DemoScriptResult {
|
|
18
|
+
/** Timeline-synced narration, one entry per narrated scene (timeline order). */
|
|
19
|
+
segments: IDemoVideoScriptSegment[];
|
|
20
|
+
/** Deterministic corrections the normalize pass applied (for observability). */
|
|
21
|
+
adjustments: string[];
|
|
22
|
+
modelUsed: ModelChoice;
|
|
23
|
+
usedFallback?: boolean;
|
|
24
|
+
/** True when the writer whiffed and we synthesized the script from narrationHints. */
|
|
25
|
+
usedHintFallback?: boolean;
|
|
26
|
+
usage?: {
|
|
27
|
+
promptTokens?: number;
|
|
28
|
+
completionTokens?: number;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Write a timeline-synced narration script for a rendered DemoSpec. Pure
|
|
33
|
+
* orchestration: build the per-scene briefs, call `.structured()`, then normalize
|
|
34
|
+
* to concrete `IDemoVideoScriptSegment[]` (compute offsets, drop unknown/empty
|
|
35
|
+
* scenes, dedupe, order). Returns an empty `segments` list (not a throw) when the
|
|
36
|
+
* model narrates nothing — the caller decides whether that means "silent final".
|
|
37
|
+
*/
|
|
38
|
+
export declare function writeDemoScript(spec: DemoSpec, deps?: DemoScriptDeps): Promise<DemoScriptResult>;
|
|
39
|
+
/**
|
|
40
|
+
* Deterministic guarantees the prompt can only request:
|
|
41
|
+
* - resolve each segment to a REAL scene (drop truly unknown sceneIds),
|
|
42
|
+
* - one segment per scene (keep the first; drop later duplicates),
|
|
43
|
+
* - drop blank narration,
|
|
44
|
+
* - stamp `startMs` (cumulative scene offset) + `durationMs` (the scene window;
|
|
45
|
+
* the voice stage overwrites `durationMs` with the actual spoken length),
|
|
46
|
+
* - emit in timeline order.
|
|
47
|
+
*
|
|
48
|
+
* `sceneId` resolution is tolerant by design. The script LLM is a separate call
|
|
49
|
+
* from the planner and has been observed to echo the scene *type* (`logoReveal`,
|
|
50
|
+
* `problem`, …) or a punctuation-drifted slug instead of the planner's exact
|
|
51
|
+
* `scene.id`. Rather than drop every segment (which silently ships a captionless,
|
|
52
|
+
* voiceover-less video), we resolve a raw id in three tiers: exact id → id with
|
|
53
|
+
* spelling drift normalized → the scene *type* when that type occurs exactly once
|
|
54
|
+
* in the timeline (so the mapping is unambiguous). Only a raw id that matches
|
|
55
|
+
* none of these is dropped. Each non-exact resolution is recorded as an
|
|
56
|
+
* adjustment for observability.
|
|
57
|
+
*/
|
|
58
|
+
export declare function normalizeDemoScript(spec: DemoSpec, rawSegments: Array<{
|
|
59
|
+
sceneId: string;
|
|
60
|
+
text: string;
|
|
61
|
+
}>): {
|
|
62
|
+
segments: IDemoVideoScriptSegment[];
|
|
63
|
+
adjustments: string[];
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=demoScriptWriter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demoScriptWriter.d.ts","sourceRoot":"","sources":["../../../src/services/demo/demoScriptWriter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAyChE,MAAM,WAAW,cAAc;IAC7B,yDAAyD;IACzD,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,uDAAuD;IACvD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,QAAQ,EAAE,uBAAuB,EAAE,CAAC;IACpC,gFAAgF;IAChF,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,WAAW,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,sFAAsF;IACtF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,KAAK,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9D;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,QAAQ,EACd,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,gBAAgB,CAAC,CA0D3B;AAOD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,QAAQ,EACd,WAAW,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GACpD;IAAE,QAAQ,EAAE,uBAAuB,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAA;CAAE,CAkGhE"}
|