vidspotai-shared 1.0.100 → 1.0.101
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 +1 -0
- package/lib/globals/aiModels/enums.d.ts.map +1 -1
- package/lib/globals/aiModels/enums.js +3 -0
- package/lib/globals/aiModels/extendCapability.d.ts +26 -0
- package/lib/globals/aiModels/extendCapability.d.ts.map +1 -0
- package/lib/globals/aiModels/extendCapability.js +42 -0
- package/lib/globals/aiModels/index.d.ts +1 -0
- package/lib/globals/aiModels/index.d.ts.map +1 -1
- package/lib/globals/aiModels/index.js +1 -0
- package/lib/globals/aiModels/providers/hostedAvatars.js +10 -10
- package/lib/globals/types.d.ts +34 -2
- package/lib/globals/types.d.ts.map +1 -1
- package/lib/globals/types.js +37 -0
- package/lib/models/demoVideoProject.model.d.ts +213 -0
- package/lib/models/demoVideoProject.model.d.ts.map +1 -1
- package/lib/models/index.d.ts +2 -0
- package/lib/models/index.d.ts.map +1 -1
- package/lib/models/index.js +2 -0
- package/lib/models/mediaUpload.model.d.ts +25 -0
- package/lib/models/mediaUpload.model.d.ts.map +1 -1
- package/lib/models/productConnection.model.d.ts +57 -0
- package/lib/models/productConnection.model.d.ts.map +1 -0
- package/lib/models/productConnection.model.js +7 -0
- package/lib/models/video.model.d.ts +11 -0
- package/lib/models/video.model.d.ts.map +1 -1
- package/lib/models/videoQaJob.model.d.ts +61 -0
- package/lib/models/videoQaJob.model.d.ts.map +1 -0
- package/lib/models/videoQaJob.model.js +16 -0
- package/lib/schemas/brandKit.schema.d.ts +3 -1
- package/lib/schemas/brandKit.schema.d.ts.map +1 -1
- package/lib/schemas/brandKit.schema.js +9 -0
- package/lib/schemas/brief.schema.d.ts +5 -4
- package/lib/schemas/brief.schema.d.ts.map +1 -1
- package/lib/schemas/brief.schema.js +23 -1
- package/lib/schemas/demoQuestion.schema.d.ts +24 -4
- package/lib/schemas/demoQuestion.schema.d.ts.map +1 -1
- package/lib/schemas/demoQuestion.schema.js +8 -2
- package/lib/schemas/demoSpec.schema.d.ts +40 -6
- package/lib/schemas/demoSpec.schema.d.ts.map +1 -1
- package/lib/schemas/demoSpec.schema.js +46 -1
- package/lib/schemas/project.schema.d.ts +33 -3
- package/lib/schemas/project.schema.d.ts.map +1 -1
- package/lib/schemas/project.schema.js +22 -0
- package/lib/schemas/social.schema.d.ts +1 -1
- package/lib/schemas/videoPlan.schema.d.ts +22 -20
- package/lib/schemas/videoPlan.schema.d.ts.map +1 -1
- package/lib/schemas/videoPlan.schema.js +19 -0
- package/lib/services/agent/editClassifier.d.ts +10 -10
- package/lib/services/agent/eval/types.d.ts +3 -2
- package/lib/services/agent/eval/types.d.ts.map +1 -1
- package/lib/services/agent/executor/core.d.ts.map +1 -1
- package/lib/services/agent/executor/core.js +11 -0
- package/lib/services/agent/planner/Planner.d.ts.map +1 -1
- package/lib/services/agent/planner/Planner.js +5 -0
- package/lib/services/agent/planner/overlayRegen.d.ts.map +1 -1
- package/lib/services/agent/planner/overlayRegen.js +5 -0
- package/lib/services/agent/planner/plannerMessages.d.ts.map +1 -1
- package/lib/services/agent/planner/plannerMessages.js +10 -1
- package/lib/services/agent/planner/scriptFirstPlanner.d.ts.map +1 -1
- package/lib/services/agent/planner/scriptFirstPlanner.js +2 -0
- package/lib/services/agent/regenCore.d.ts.map +1 -1
- package/lib/services/agent/regenCore.js +11 -1
- package/lib/services/agent/runHelpers.d.ts +8 -0
- package/lib/services/agent/runHelpers.d.ts.map +1 -1
- package/lib/services/agent/runHelpers.js +5 -1
- package/lib/services/agent/tools/chapterOutline.tool.d.ts +8 -0
- package/lib/services/agent/tools/chapterOutline.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/chapterOutline.tool.js +21 -8
- package/lib/services/agent/tools/composeScene.tool.d.ts +32 -2
- package/lib/services/agent/tools/composeScene.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/estimateCost.tool.d.ts +11 -1
- package/lib/services/agent/tools/estimateCost.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/extendVideo.tool.d.ts +9 -3
- package/lib/services/agent/tools/extendVideo.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/extendVideo.tool.js +12 -27
- package/lib/services/agent/tools/generateAvatarVideo.tool.d.ts +1 -1
- package/lib/services/agent/tools/generateScript.tool.d.ts +3 -2
- package/lib/services/agent/tools/generateScript.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/generateScript.tool.js +16 -7
- package/lib/services/agent/tools/generateVoiceover.tool.d.ts +1 -1
- package/lib/services/agent/tools/planVideo.tool.d.ts +11 -8
- package/lib/services/agent/tools/planVideo.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/render.tool.d.ts +11 -1
- package/lib/services/agent/tools/render.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/searchStock.tool.d.ts +1 -1
- package/lib/services/agent/tools/searchUserLibrary.tool.d.ts +4 -4
- package/lib/services/aiGen/providers/kling/kling.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/kling/kling.service.js +22 -3
- package/lib/services/aiGen/providers/kling/klingStatus.d.ts +7 -0
- package/lib/services/aiGen/providers/kling/klingStatus.d.ts.map +1 -1
- package/lib/services/aiGen/providers/kling/klingStatus.js +41 -4
- package/lib/services/aiGen/providers/pixverse/pixverse.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/pixverse/pixverse.service.js +25 -0
- package/lib/services/aiGen/providers/types.d.ts +20 -0
- package/lib/services/aiGen/providers/types.d.ts.map +1 -1
- package/lib/services/avatarGen/avatarProviderFactory.d.ts.map +1 -1
- package/lib/services/avatarGen/avatarProviderFactory.js +3 -0
- package/lib/services/avatarGen/index.d.ts +1 -0
- package/lib/services/avatarGen/index.d.ts.map +1 -1
- package/lib/services/avatarGen/index.js +1 -0
- package/lib/services/avatarGen/providers/synthesiaAvatarProvider.d.ts +53 -0
- package/lib/services/avatarGen/providers/synthesiaAvatarProvider.d.ts.map +1 -0
- package/lib/services/avatarGen/providers/synthesiaAvatarProvider.js +176 -0
- package/lib/services/demo/demoAuthor.d.ts +7 -0
- package/lib/services/demo/demoAuthor.d.ts.map +1 -1
- package/lib/services/demo/demoAuthor.js +2 -0
- package/lib/services/demo/demoAuthorPrompt.d.ts +6 -0
- package/lib/services/demo/demoAuthorPrompt.d.ts.map +1 -1
- package/lib/services/demo/demoAuthorPrompt.js +24 -2
- package/lib/services/demo/demoCaptions.d.ts +69 -0
- package/lib/services/demo/demoCaptions.d.ts.map +1 -0
- package/lib/services/demo/demoCaptions.js +152 -0
- package/lib/services/demo/demoMusic.d.ts +8 -1
- package/lib/services/demo/demoMusic.d.ts.map +1 -1
- package/lib/services/demo/demoMusic.js +32 -16
- package/lib/services/demo/demoScriptPrompt.d.ts +2 -1
- package/lib/services/demo/demoScriptPrompt.d.ts.map +1 -1
- package/lib/services/demo/demoScriptPrompt.js +29 -2
- package/lib/services/demo/demoStockAssets.d.ts +29 -0
- package/lib/services/demo/demoStockAssets.d.ts.map +1 -0
- package/lib/services/demo/demoStockAssets.js +107 -0
- package/lib/services/demo/index.d.ts +3 -0
- package/lib/services/demo/index.d.ts.map +1 -1
- package/lib/services/demo/index.js +3 -0
- package/lib/services/demo/productConnection.service.d.ts +78 -0
- package/lib/services/demo/productConnection.service.d.ts.map +1 -0
- package/lib/services/demo/productConnection.service.js +125 -0
- package/lib/services/editor/planToProject.d.ts.map +1 -1
- package/lib/services/editor/planToProject.helpers.d.ts +1 -0
- package/lib/services/editor/planToProject.helpers.d.ts.map +1 -1
- package/lib/services/editor/planToProject.helpers.js +3 -0
- package/lib/services/editor/planToProject.js +39 -0
- package/lib/services/editor/projectToDesign.js +13 -1
- package/lib/services/firestore.service.d.ts +14 -0
- package/lib/services/firestore.service.d.ts.map +1 -1
- package/lib/services/firestore.service.js +16 -0
- package/lib/services/translation/index.d.ts +1 -0
- package/lib/services/translation/index.d.ts.map +1 -1
- package/lib/services/translation/index.js +8 -1
- package/lib/services/translation/outputLanguage.d.ts +75 -0
- package/lib/services/translation/outputLanguage.d.ts.map +1 -0
- package/lib/services/translation/outputLanguage.js +190 -0
- package/lib/services/translation/translation.service.d.ts +13 -2
- package/lib/services/translation/translation.service.d.ts.map +1 -1
- package/lib/services/translation/translation.service.js +66 -5
- package/lib/services/tts/index.d.ts +1 -0
- package/lib/services/tts/index.d.ts.map +1 -1
- package/lib/services/tts/index.js +1 -0
- package/lib/services/tts/providers/elevenlabs.service.d.ts +7 -0
- package/lib/services/tts/providers/elevenlabs.service.d.ts.map +1 -1
- package/lib/services/tts/providers/elevenlabs.service.js +69 -13
- package/lib/services/tts/types.d.ts +29 -0
- package/lib/services/tts/types.d.ts.map +1 -1
- package/lib/services/tts/wordTimings.d.ts +27 -0
- package/lib/services/tts/wordTimings.d.ts.map +1 -0
- package/lib/services/tts/wordTimings.js +57 -0
- package/lib/utils/billableDuration.d.ts +11 -0
- package/lib/utils/billableDuration.d.ts.map +1 -0
- package/lib/utils/billableDuration.js +91 -0
- package/lib/utils/index.d.ts +3 -0
- package/lib/utils/index.d.ts.map +1 -1
- package/lib/utils/index.js +3 -0
- package/lib/utils/mp4Duration.d.ts +38 -0
- package/lib/utils/mp4Duration.d.ts.map +1 -0
- package/lib/utils/mp4Duration.js +164 -0
- package/lib/utils/socialEntitlements.d.ts +51 -0
- package/lib/utils/socialEntitlements.d.ts.map +1 -0
- package/lib/utils/socialEntitlements.js +65 -0
- package/package.json +1 -1
|
@@ -46,23 +46,29 @@ function moodForStyle(spec) {
|
|
|
46
46
|
* a dead CDN edge silently ships no music rather than 404-crashing the mux.
|
|
47
47
|
* (Same guard shape as `selectMusicForPlan`'s `pickFirstReachable`.)
|
|
48
48
|
*/
|
|
49
|
+
async function urlReachable(url) {
|
|
50
|
+
try {
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
53
|
+
const r = await fetch(url, { method: "HEAD", signal: controller.signal });
|
|
54
|
+
clearTimeout(timeout);
|
|
55
|
+
if (r.ok)
|
|
56
|
+
return true;
|
|
57
|
+
logger_1.logger.warn(`${TAG} track HEAD non-2xx, skipping`, { url, status: r.status });
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
logger_1.logger.warn(`${TAG} track HEAD failed, skipping`, {
|
|
62
|
+
url,
|
|
63
|
+
err: e instanceof Error ? e.message : String(e),
|
|
64
|
+
});
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
49
68
|
async function firstReachable(tracks) {
|
|
50
69
|
for (const t of tracks) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
54
|
-
const r = await fetch(t.url, { method: "HEAD", signal: controller.signal });
|
|
55
|
-
clearTimeout(timeout);
|
|
56
|
-
if (r.ok)
|
|
57
|
-
return t;
|
|
58
|
-
logger_1.logger.warn(`${TAG} track HEAD non-2xx, skipping`, { url: t.url, status: r.status });
|
|
59
|
-
}
|
|
60
|
-
catch (e) {
|
|
61
|
-
logger_1.logger.warn(`${TAG} track HEAD failed, skipping`, {
|
|
62
|
-
url: t.url,
|
|
63
|
-
err: e instanceof Error ? e.message : String(e),
|
|
64
|
-
});
|
|
65
|
-
}
|
|
70
|
+
if (await urlReachable(t.url))
|
|
71
|
+
return t;
|
|
66
72
|
}
|
|
67
73
|
return undefined;
|
|
68
74
|
}
|
|
@@ -70,7 +76,17 @@ async function firstReachable(tracks) {
|
|
|
70
76
|
* Pick a reachable background-music bed for a demo. Returns null (never throws)
|
|
71
77
|
* when nothing is reachable — the caller ships the video without a bed.
|
|
72
78
|
*/
|
|
73
|
-
async function selectDemoMusic(spec) {
|
|
79
|
+
async function selectDemoMusic(spec, opts) {
|
|
80
|
+
// User-provided track wins when reachable (still fail-soft to the catalog).
|
|
81
|
+
if (opts?.preferredUrl && (await urlReachable(opts.preferredUrl))) {
|
|
82
|
+
logger_1.logger.info(`${TAG} using user-provided bed`, { url: opts.preferredUrl });
|
|
83
|
+
return { url: opts.preferredUrl, attribution: "User-provided" };
|
|
84
|
+
}
|
|
85
|
+
if (opts?.preferredUrl) {
|
|
86
|
+
logger_1.logger.warn(`${TAG} user bed unreachable — falling back to catalog`, {
|
|
87
|
+
url: opts.preferredUrl,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
74
90
|
const { mood, query, bpmTarget } = moodForStyle(spec);
|
|
75
91
|
try {
|
|
76
92
|
// Don't pass durationSecMin — every curated track is looped to the video
|
|
@@ -36,7 +36,8 @@ export declare function wordCount(text: string): number;
|
|
|
36
36
|
* 1. keep as many WHOLE sentences as fit within budget;
|
|
37
37
|
* 2. if even the first sentence is over budget, cap at `budget` words and back
|
|
38
38
|
* off to the last clause boundary (comma/semicolon/dash) in that span;
|
|
39
|
-
* 3.
|
|
39
|
+
* 3. drop any dangling function-word tail so the line ends on a content word;
|
|
40
|
+
* 4. always end on terminal punctuation.
|
|
40
41
|
* Never lengthens. Returns the (possibly) trimmed text + whether it changed.
|
|
41
42
|
*/
|
|
42
43
|
export declare function fitNarrationToBudget(text: string, budget: number): {
|
|
@@ -1 +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;
|
|
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;AA0DD;;;;;;;;;;;;;;GAcG;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"}
|
|
@@ -72,6 +72,32 @@ function splitSentences(text) {
|
|
|
72
72
|
out.push(text.slice(last).trim());
|
|
73
73
|
return out.filter(Boolean);
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Words that must never END a trimmed line: cutting here yields a dangling
|
|
77
|
+
* fragment ("…describe what you want and.") that reads as broken copy and sounds
|
|
78
|
+
* worse spoken than a shorter, complete phrase. Conjunctions, prepositions,
|
|
79
|
+
* articles, and auxiliaries all imply a continuation that was cut away.
|
|
80
|
+
*/
|
|
81
|
+
const DANGLING_TAIL_WORDS = new Set([
|
|
82
|
+
"a", "an", "and", "as", "at", "but", "by", "for", "from", "if", "in", "into",
|
|
83
|
+
"is", "it", "its", "of", "on", "or", "so", "than", "that", "the", "then",
|
|
84
|
+
"to", "up", "was", "were", "when", "which", "while", "with", "you", "your",
|
|
85
|
+
]);
|
|
86
|
+
/**
|
|
87
|
+
* Drop trailing function words so a capped span ends on a content word. Bounded:
|
|
88
|
+
* never strips below half the words it was given, so a line made entirely of
|
|
89
|
+
* function words degrades to the plain cap rather than to nothing.
|
|
90
|
+
*/
|
|
91
|
+
function trimDanglingTail(text) {
|
|
92
|
+
const words = text.trim().split(/\s+/).filter(Boolean);
|
|
93
|
+
const floor = Math.ceil(words.length / 2);
|
|
94
|
+
let end = words.length;
|
|
95
|
+
while (end > floor &&
|
|
96
|
+
DANGLING_TAIL_WORDS.has(words[end - 1].toLowerCase().replace(/[^\p{L}']/gu, ""))) {
|
|
97
|
+
end--;
|
|
98
|
+
}
|
|
99
|
+
return words.slice(0, end).join(" ");
|
|
100
|
+
}
|
|
75
101
|
/**
|
|
76
102
|
* Deterministically fit narration to a spoken-word `budget` so the synthesized
|
|
77
103
|
* speech lands INSIDE its scene window — instead of overrunning and either
|
|
@@ -83,7 +109,8 @@ function splitSentences(text) {
|
|
|
83
109
|
* 1. keep as many WHOLE sentences as fit within budget;
|
|
84
110
|
* 2. if even the first sentence is over budget, cap at `budget` words and back
|
|
85
111
|
* off to the last clause boundary (comma/semicolon/dash) in that span;
|
|
86
|
-
* 3.
|
|
112
|
+
* 3. drop any dangling function-word tail so the line ends on a content word;
|
|
113
|
+
* 4. always end on terminal punctuation.
|
|
87
114
|
* Never lengthens. Returns the (possibly) trimmed text + whether it changed.
|
|
88
115
|
*/
|
|
89
116
|
function fitNarrationToBudget(text, budget) {
|
|
@@ -110,7 +137,7 @@ function fitNarrationToBudget(text, budget) {
|
|
|
110
137
|
const out = clauseIdx > 0 && wordCount(capped.slice(0, clauseIdx)) >= Math.ceil(budget * 0.5)
|
|
111
138
|
? capped.slice(0, clauseIdx)
|
|
112
139
|
: capped;
|
|
113
|
-
return { text: ensureTerminalPunctuation(out), trimmed: true };
|
|
140
|
+
return { text: ensureTerminalPunctuation(trimDanglingTail(out)), trimmed: true };
|
|
114
141
|
}
|
|
115
142
|
/** Pull the salient on-screen copy from a scene so narration can complement it. */
|
|
116
143
|
function sceneCopyDigest(scene) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stock-asset resolution for the SaaS Demo Video pipeline — the "real library"
|
|
3
|
+
* half of the planner's `generatedAssets` (see DemoGeneratedAssetSchema, where
|
|
4
|
+
* `source:"stock"` requests a real photo/clip instead of an AI generation).
|
|
5
|
+
*
|
|
6
|
+
* Free, instant, and REAL: for generic real-world subjects (a laptop on a desk,
|
|
7
|
+
* a city street, a coffee shop) a licensed stock shot beats a synthesized one —
|
|
8
|
+
* so the planner can prefer it and skip the generation cost. Resolution reuses
|
|
9
|
+
* the same provider cascade the agent flow uses (Pexels → Pixabay → Unsplash for
|
|
10
|
+
* images; Pexels → Pixabay for video), gated on env keys. Fail-soft: returns
|
|
11
|
+
* null when nothing is reachable/available, and the caller marks the asset failed
|
|
12
|
+
* and strips its `gen://` ref so the scene degrades to its coded surface.
|
|
13
|
+
*
|
|
14
|
+
* Kept in `shared` (next to demoMusic) so it can import the provider services via
|
|
15
|
+
* relative paths — they are not part of the package's public surface. Deliberately
|
|
16
|
+
* cache-free: demo runs are infrequent and each stock ref resolves once per spec.
|
|
17
|
+
*/
|
|
18
|
+
import type { DemoGeneratedAsset, DemoSpec } from "../../schemas/demoSpec.schema";
|
|
19
|
+
/** Whether any stock provider is configured — the prompt only offers stock when true. */
|
|
20
|
+
export declare function stockAssetsAvailable(): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve one `source:"stock"` asset to a real media URL, trying each configured
|
|
23
|
+
* provider in turn. Returns null (never throws) when no provider yields a hit.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveStockAsset(request: DemoGeneratedAsset, spec: DemoSpec): Promise<{
|
|
26
|
+
url: string;
|
|
27
|
+
attribution?: string;
|
|
28
|
+
} | null>;
|
|
29
|
+
//# sourceMappingURL=demoStockAssets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demoStockAssets.d.ts","sourceRoot":"","sources":["../../../src/services/demo/demoStockAssets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAYlF,yFAAyF;AACzF,wBAAgB,oBAAoB,IAAI,OAAO,CAM9C;AAkDD;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CA+CvD"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stockAssetsAvailable = stockAssetsAvailable;
|
|
4
|
+
exports.resolveStockAsset = resolveStockAsset;
|
|
5
|
+
const pexels_service_1 = require("../aiGen/providers/pexels/pexels.service");
|
|
6
|
+
const pixabay_service_1 = require("../aiGen/providers/pixabay/pixabay.service");
|
|
7
|
+
const unsplash_service_1 = require("../aiGen/providers/unsplash/unsplash.service");
|
|
8
|
+
const logger_1 = require("../../utils/logger");
|
|
9
|
+
const TAG = "[DemoStockAssets]";
|
|
10
|
+
/** Whether any stock provider is configured — the prompt only offers stock when true. */
|
|
11
|
+
function stockAssetsAvailable() {
|
|
12
|
+
return Boolean(process.env.PEXELS_API_KEY ||
|
|
13
|
+
process.env.PIXABAY_API_KEY ||
|
|
14
|
+
process.env.UNSPLASH_ACCESS_KEY);
|
|
15
|
+
}
|
|
16
|
+
/** Provider cascade — same order/precedence as the agent's search_stock tool. */
|
|
17
|
+
function buildChain(kind) {
|
|
18
|
+
const pexels = new pexels_service_1.PexelsProviderService();
|
|
19
|
+
const pixabay = new pixabay_service_1.PixabayProviderService();
|
|
20
|
+
const unsplash = new unsplash_service_1.UnsplashProviderService();
|
|
21
|
+
const chain = [
|
|
22
|
+
{
|
|
23
|
+
name: "pexels",
|
|
24
|
+
available: () => Boolean(process.env.PEXELS_API_KEY),
|
|
25
|
+
supports: () => true,
|
|
26
|
+
search: (p) => pexels.searchStock(p),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "pixabay",
|
|
30
|
+
available: () => Boolean(process.env.PIXABAY_API_KEY),
|
|
31
|
+
supports: () => true,
|
|
32
|
+
search: (p) => pixabay.searchStock(p),
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "unsplash",
|
|
36
|
+
available: () => Boolean(process.env.UNSPLASH_ACCESS_KEY),
|
|
37
|
+
supports: (k) => k === "image",
|
|
38
|
+
search: (p) => unsplash.searchStock(p),
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
return chain.filter((l) => l.supports(kind) && l.available());
|
|
42
|
+
}
|
|
43
|
+
/** Plain search terms from the request (`query` wins; else the prompt's first words). */
|
|
44
|
+
function searchQuery(request) {
|
|
45
|
+
const q = request.query?.trim();
|
|
46
|
+
if (q)
|
|
47
|
+
return q;
|
|
48
|
+
// Fallback: first ~6 words of the generation prompt, stripped of punctuation.
|
|
49
|
+
return request.prompt
|
|
50
|
+
.replace(/[^\p{L}\p{N}\s]/gu, " ")
|
|
51
|
+
.split(/\s+/)
|
|
52
|
+
.filter(Boolean)
|
|
53
|
+
.slice(0, 6)
|
|
54
|
+
.join(" ");
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve one `source:"stock"` asset to a real media URL, trying each configured
|
|
58
|
+
* provider in turn. Returns null (never throws) when no provider yields a hit.
|
|
59
|
+
*/
|
|
60
|
+
async function resolveStockAsset(request, spec) {
|
|
61
|
+
const kind = request.kind; // "image" | "video"
|
|
62
|
+
const chain = buildChain(kind);
|
|
63
|
+
if (chain.length === 0) {
|
|
64
|
+
logger_1.logger.warn(`${TAG} no stock provider configured — cannot resolve`, {
|
|
65
|
+
id: request.id,
|
|
66
|
+
kind,
|
|
67
|
+
});
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const params = {
|
|
71
|
+
query: searchQuery(request),
|
|
72
|
+
kind,
|
|
73
|
+
aspectRatio: request.aspect ?? spec.aspect,
|
|
74
|
+
perPage: 5,
|
|
75
|
+
...(kind === "video"
|
|
76
|
+
? { minDurationSec: 2, maxDurationSec: request.durationSec ?? 15 }
|
|
77
|
+
: {}),
|
|
78
|
+
};
|
|
79
|
+
for (const link of chain) {
|
|
80
|
+
try {
|
|
81
|
+
const result = await link.search(params);
|
|
82
|
+
const asset = result.assets?.[0];
|
|
83
|
+
if (asset?.url) {
|
|
84
|
+
logger_1.logger.info(`${TAG} resolved ${kind} "${request.id}" via ${link.name}`, {
|
|
85
|
+
query: params.query,
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
url: asset.url,
|
|
89
|
+
...(asset.attribution?.name
|
|
90
|
+
? { attribution: `${asset.attribution.name} (${asset.source})` }
|
|
91
|
+
: {}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
logger_1.logger.warn(`${TAG} ${link.name} returned no results`, {
|
|
95
|
+
id: request.id,
|
|
96
|
+
query: params.query,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
logger_1.logger.warn(`${TAG} ${link.name} search failed — trying next`, {
|
|
101
|
+
id: request.id,
|
|
102
|
+
err: err instanceof Error ? err.message : String(err),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
@@ -13,7 +13,9 @@ export * from "./demoAuthor";
|
|
|
13
13
|
export * from "./demoCredits";
|
|
14
14
|
export * from "./demoScriptPrompt";
|
|
15
15
|
export * from "./demoScriptWriter";
|
|
16
|
+
export * from "./demoCaptions";
|
|
16
17
|
export * from "./demoMusic";
|
|
18
|
+
export * from "./demoStockAssets";
|
|
17
19
|
export * from "./demoMediaGuard";
|
|
18
20
|
export * from "./demoRevisePrompt";
|
|
19
21
|
export * from "./demoReviser";
|
|
@@ -21,4 +23,5 @@ export * from "./demoPrefill";
|
|
|
21
23
|
export * from "./demoTriage";
|
|
22
24
|
export * from "./githubOAuth";
|
|
23
25
|
export * from "./githubOAuthState";
|
|
26
|
+
export * from "./productConnection.service";
|
|
24
27
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/demo/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/demo/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,6BAA6B,CAAC"}
|
|
@@ -29,7 +29,9 @@ __exportStar(require("./demoAuthor"), exports);
|
|
|
29
29
|
__exportStar(require("./demoCredits"), exports);
|
|
30
30
|
__exportStar(require("./demoScriptPrompt"), exports);
|
|
31
31
|
__exportStar(require("./demoScriptWriter"), exports);
|
|
32
|
+
__exportStar(require("./demoCaptions"), exports);
|
|
32
33
|
__exportStar(require("./demoMusic"), exports);
|
|
34
|
+
__exportStar(require("./demoStockAssets"), exports);
|
|
33
35
|
__exportStar(require("./demoMediaGuard"), exports);
|
|
34
36
|
__exportStar(require("./demoRevisePrompt"), exports);
|
|
35
37
|
__exportStar(require("./demoReviser"), exports);
|
|
@@ -37,3 +39,4 @@ __exportStar(require("./demoPrefill"), exports);
|
|
|
37
39
|
__exportStar(require("./demoTriage"), exports);
|
|
38
40
|
__exportStar(require("./githubOAuth"), exports);
|
|
39
41
|
__exportStar(require("./githubOAuthState"), exports);
|
|
42
|
+
__exportStar(require("./productConnection.service"), exports);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { IProductConnectionModel } from "../../models/productConnection.model";
|
|
2
|
+
/**
|
|
3
|
+
* Product sign-in connection store (Tier 2 product-footage-capture —
|
|
4
|
+
* notes/PRODUCT_FOOTAGE_CAPTURE_PLAN.md). Owns the encrypt-at-write /
|
|
5
|
+
* decrypt-at-read of a user's captured browser sign-in session so both the
|
|
6
|
+
* live-session capture endpoint AND the worker's authed replay go through one
|
|
7
|
+
* place. Mirrors the githubConnection token flow (AES-256-GCM via the shared
|
|
8
|
+
* vault, `SOCIAL_TOKEN_ENC_KEY`) — plaintext state never touches Firestore.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* A Playwright-compatible browser storage snapshot — the RESULT of a sign-in
|
|
12
|
+
* (never credentials). Shape matches Playwright's `storageState()` so the worker
|
|
13
|
+
* can seed cookies + localStorage before navigating past the auth wall.
|
|
14
|
+
*/
|
|
15
|
+
export interface ProductStorageState {
|
|
16
|
+
cookies: Array<{
|
|
17
|
+
name: string;
|
|
18
|
+
value: string;
|
|
19
|
+
domain: string;
|
|
20
|
+
path: string;
|
|
21
|
+
/** Unix seconds; -1 / absent for session cookies. */
|
|
22
|
+
expires?: number;
|
|
23
|
+
httpOnly?: boolean;
|
|
24
|
+
secure?: boolean;
|
|
25
|
+
sameSite?: "Strict" | "Lax" | "None";
|
|
26
|
+
}>;
|
|
27
|
+
origins: Array<{
|
|
28
|
+
origin: string;
|
|
29
|
+
localStorage: Array<{
|
|
30
|
+
name: string;
|
|
31
|
+
value: string;
|
|
32
|
+
}>;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
/** Public-safe view of a connection — everything EXCEPT the encrypted state. */
|
|
36
|
+
export interface ProductConnectionSummary {
|
|
37
|
+
host: string;
|
|
38
|
+
productUrl: string;
|
|
39
|
+
origins: string[];
|
|
40
|
+
label?: string;
|
|
41
|
+
status: IProductConnectionModel["status"];
|
|
42
|
+
expiresHint?: number;
|
|
43
|
+
}
|
|
44
|
+
/** Origin host of a URL, lowercased, or null if unparseable. */
|
|
45
|
+
export declare function hostOf(url: string): string | null;
|
|
46
|
+
/** True when the vault is configured — connections are unusable without it. */
|
|
47
|
+
export declare function isProductConnectionEnabled(): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Upsert a captured sign-in session (encrypted). Idempotent per (user, host):
|
|
50
|
+
* re-connecting the same product replaces the expired snapshot. Returns the
|
|
51
|
+
* host used as the connection key.
|
|
52
|
+
*/
|
|
53
|
+
export declare function saveProductConnection(args: {
|
|
54
|
+
userId: string;
|
|
55
|
+
productUrl: string;
|
|
56
|
+
storageState: ProductStorageState;
|
|
57
|
+
label?: string;
|
|
58
|
+
}): Promise<{
|
|
59
|
+
host: string;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Read + decrypt a user's connection for a host. Returns the storageState +
|
|
63
|
+
* metadata, or null when there's no ACTIVE connection / the vault can't decrypt
|
|
64
|
+
* (best-effort — the caller treats null as "not connected" and asks the user).
|
|
65
|
+
*/
|
|
66
|
+
export declare function getProductConnection(userId: string, host: string): Promise<{
|
|
67
|
+
state: ProductStorageState;
|
|
68
|
+
conn: IProductConnectionModel;
|
|
69
|
+
} | null>;
|
|
70
|
+
/** List a user's connections as public-safe summaries (no encrypted state). */
|
|
71
|
+
export declare function listProductConnections(userId: string): Promise<ProductConnectionSummary[]>;
|
|
72
|
+
/** Delete (revoke) a user's connection for a host. */
|
|
73
|
+
export declare function deleteProductConnection(userId: string, host: string): Promise<void>;
|
|
74
|
+
/** Flag a connection expired after a replay found the session no longer authenticates. */
|
|
75
|
+
export declare function markProductConnectionExpired(userId: string, host: string): Promise<void>;
|
|
76
|
+
/** Stamp lastUsedAt after a successful replay (observability; best-effort). */
|
|
77
|
+
export declare function touchProductConnection(userId: string, host: string): Promise<void>;
|
|
78
|
+
//# sourceMappingURL=productConnection.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"productConnection.service.d.ts","sourceRoot":"","sources":["../../../src/services/demo/productConnection.service.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,uBAAuB,EAExB,MAAM,sCAAsC,CAAC;AAG9C;;;;;;;GAOG;AAEH;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,qDAAqD;QACrD,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;KACtC,CAAC,CAAC;IACH,OAAO,EAAE,KAAK,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACtD,CAAC,CAAC;CACJ;AAED,gFAAgF;AAChF,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,gEAAgE;AAChE,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMjD;AAWD,+EAA+E;AAC/E,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,mBAAmB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAgC5B;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,KAAK,EAAE,mBAAmB,CAAC;IAAC,IAAI,EAAE,uBAAuB,CAAA;CAAE,GAAG,IAAI,CAAC,CAY/E;AAED,+EAA+E;AAC/E,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAerC;AAED,sDAAsD;AACtD,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,0FAA0F;AAC1F,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,+EAA+E;AAC/E,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAQf"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hostOf = hostOf;
|
|
4
|
+
exports.isProductConnectionEnabled = isProductConnectionEnabled;
|
|
5
|
+
exports.saveProductConnection = saveProductConnection;
|
|
6
|
+
exports.getProductConnection = getProductConnection;
|
|
7
|
+
exports.listProductConnections = listProductConnections;
|
|
8
|
+
exports.deleteProductConnection = deleteProductConnection;
|
|
9
|
+
exports.markProductConnectionExpired = markProductConnectionExpired;
|
|
10
|
+
exports.touchProductConnection = touchProductConnection;
|
|
11
|
+
const firestore_1 = require("firebase-admin/firestore");
|
|
12
|
+
const firestore_service_1 = require("../firestore.service");
|
|
13
|
+
const productConnection_model_1 = require("../../models/productConnection.model");
|
|
14
|
+
const tokenVault_1 = require("../crypto/tokenVault");
|
|
15
|
+
/** Origin host of a URL, lowercased, or null if unparseable. */
|
|
16
|
+
function hostOf(url) {
|
|
17
|
+
try {
|
|
18
|
+
return new URL(url).host.toLowerCase();
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Earliest real cookie expiry (epoch ms) across the snapshot, for the reconnect UX. */
|
|
25
|
+
function earliestCookieExpiry(state) {
|
|
26
|
+
const future = state.cookies
|
|
27
|
+
.map((c) => c.expires)
|
|
28
|
+
.filter((e) => typeof e === "number" && e > 0)
|
|
29
|
+
.map((e) => e * 1000);
|
|
30
|
+
return future.length ? Math.min(...future) : undefined;
|
|
31
|
+
}
|
|
32
|
+
/** True when the vault is configured — connections are unusable without it. */
|
|
33
|
+
function isProductConnectionEnabled() {
|
|
34
|
+
return (0, tokenVault_1.isTokenVaultConfigured)();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Upsert a captured sign-in session (encrypted). Idempotent per (user, host):
|
|
38
|
+
* re-connecting the same product replaces the expired snapshot. Returns the
|
|
39
|
+
* host used as the connection key.
|
|
40
|
+
*/
|
|
41
|
+
async function saveProductConnection(args) {
|
|
42
|
+
const { userId, productUrl, storageState, label } = args;
|
|
43
|
+
const host = hostOf(productUrl);
|
|
44
|
+
if (!host)
|
|
45
|
+
throw new Error("saveProductConnection: unparseable productUrl");
|
|
46
|
+
const origins = [
|
|
47
|
+
...new Set(storageState.origins
|
|
48
|
+
.map((o) => o.origin)
|
|
49
|
+
.concat(new URL(productUrl).origin)),
|
|
50
|
+
];
|
|
51
|
+
const expiresHint = earliestCookieExpiry(storageState);
|
|
52
|
+
const stateEnc = (0, tokenVault_1.encryptToken)(JSON.stringify(storageState));
|
|
53
|
+
const doc = {
|
|
54
|
+
userId,
|
|
55
|
+
host,
|
|
56
|
+
productUrl,
|
|
57
|
+
origins,
|
|
58
|
+
...(label ? { label } : {}),
|
|
59
|
+
stateEnc,
|
|
60
|
+
...(expiresHint ? { expiresHint } : {}),
|
|
61
|
+
status: "active",
|
|
62
|
+
capturedAt: firestore_1.FieldValue.serverTimestamp(),
|
|
63
|
+
updatedAt: firestore_1.FieldValue.serverTimestamp(),
|
|
64
|
+
};
|
|
65
|
+
await firestore_service_1.FirestoreService.productConnectionsCol
|
|
66
|
+
.doc((0, productConnection_model_1.productConnectionDocId)(userId, host))
|
|
67
|
+
.set(doc);
|
|
68
|
+
return { host };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Read + decrypt a user's connection for a host. Returns the storageState +
|
|
72
|
+
* metadata, or null when there's no ACTIVE connection / the vault can't decrypt
|
|
73
|
+
* (best-effort — the caller treats null as "not connected" and asks the user).
|
|
74
|
+
*/
|
|
75
|
+
async function getProductConnection(userId, host) {
|
|
76
|
+
const snap = await firestore_service_1.FirestoreService.productConnectionsCol
|
|
77
|
+
.doc((0, productConnection_model_1.productConnectionDocId)(userId, host))
|
|
78
|
+
.get();
|
|
79
|
+
const conn = snap.data();
|
|
80
|
+
if (!conn || conn.status !== "active" || !conn.stateEnc)
|
|
81
|
+
return null;
|
|
82
|
+
try {
|
|
83
|
+
const state = JSON.parse((0, tokenVault_1.decryptToken)(conn.stateEnc));
|
|
84
|
+
return { state, conn };
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** List a user's connections as public-safe summaries (no encrypted state). */
|
|
91
|
+
async function listProductConnections(userId) {
|
|
92
|
+
const snap = await firestore_service_1.FirestoreService.productConnectionsCol
|
|
93
|
+
.where("userId", "==", userId)
|
|
94
|
+
.get();
|
|
95
|
+
return snap.docs.map((d) => {
|
|
96
|
+
const c = d.data();
|
|
97
|
+
return {
|
|
98
|
+
host: c.host,
|
|
99
|
+
productUrl: c.productUrl,
|
|
100
|
+
origins: c.origins,
|
|
101
|
+
...(c.label ? { label: c.label } : {}),
|
|
102
|
+
status: c.status,
|
|
103
|
+
...(c.expiresHint ? { expiresHint: c.expiresHint } : {}),
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/** Delete (revoke) a user's connection for a host. */
|
|
108
|
+
async function deleteProductConnection(userId, host) {
|
|
109
|
+
await firestore_service_1.FirestoreService.productConnectionsCol
|
|
110
|
+
.doc((0, productConnection_model_1.productConnectionDocId)(userId, host))
|
|
111
|
+
.delete();
|
|
112
|
+
}
|
|
113
|
+
/** Flag a connection expired after a replay found the session no longer authenticates. */
|
|
114
|
+
async function markProductConnectionExpired(userId, host) {
|
|
115
|
+
await firestore_service_1.FirestoreService.productConnectionsCol
|
|
116
|
+
.doc((0, productConnection_model_1.productConnectionDocId)(userId, host))
|
|
117
|
+
.set({ status: "expired", updatedAt: firestore_1.FieldValue.serverTimestamp() }, { merge: true });
|
|
118
|
+
}
|
|
119
|
+
/** Stamp lastUsedAt after a successful replay (observability; best-effort). */
|
|
120
|
+
async function touchProductConnection(userId, host) {
|
|
121
|
+
await firestore_service_1.FirestoreService.productConnectionsCol
|
|
122
|
+
.doc((0, productConnection_model_1.productConnectionDocId)(userId, host))
|
|
123
|
+
.set({ lastUsedAt: firestore_1.FieldValue.serverTimestamp(), updatedAt: firestore_1.FieldValue.serverTimestamp() }, { merge: true })
|
|
124
|
+
.catch(() => { });
|
|
125
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planToProject.d.ts","sourceRoot":"","sources":["../../../src/services/editor/planToProject.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKV,OAAO,EAIR,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAehE,OAAO,EAAE,QAAQ,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEpF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"planToProject.d.ts","sourceRoot":"","sources":["../../../src/services/editor/planToProject.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKV,OAAO,EAIR,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAehE,OAAO,EAAE,QAAQ,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEpF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CA6bhE"}
|
|
@@ -15,6 +15,7 @@ export declare const TRACK_ID: {
|
|
|
15
15
|
readonly music: "track-music";
|
|
16
16
|
readonly text: "track-text";
|
|
17
17
|
readonly caption: "track-caption";
|
|
18
|
+
readonly logo: "track-logo";
|
|
18
19
|
};
|
|
19
20
|
export declare function isStillStrategy(s: SceneAssetStrategy): boolean;
|
|
20
21
|
/** A passable URL we can put in skeleton clips so ProjectSchema.url() passes. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planToProject.helpers.d.ts","sourceRoot":"","sources":["../../../src/services/editor/planToProject.helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,EACR,KAAK,EACL,UAAU,EACX,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,cAAc,EACd,SAAS,EACV,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE;;;;;;GAMG;AAIH,eAAO,MAAM,QAAQ
|
|
1
|
+
{"version":3,"file":"planToProject.helpers.d.ts","sourceRoot":"","sources":["../../../src/services/editor/planToProject.helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,EACR,KAAK,EACL,UAAU,EACX,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,cAAc,EACd,SAAS,EACV,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE;;;;;;GAMG;AAIH,eAAO,MAAM,QAAQ;;;;;;;;CAYX,CAAC;AASX,wBAAgB,eAAe,CAAC,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAE9D;AAED,iFAAiF;AACjF,eAAO,MAAM,eAAe,sBAAsB,CAAC;AAInD,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAW5F;AAID,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,cAAc,EAAE,UAAU,CAAC,MAAM,CAAC,GAAG,IAAI,CASjF,CAAC;AAIF,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,kBAAkB,GAAG,SAAS,GACtC,UAAU,CAiBZ;AA4CD;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,WAAW,EAAE,MAAM;AACnB;;;;GAIG;AACH,SAAS,CAAC,EAAE,aAAa,GACxB,QAAQ,EAAE,CA2CZ;AAED,wBAAgB,SAAS,CACvB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,EACnB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,aAAa,CAAC,EAAE,MAAM,GACrB,KAAK,CAYP"}
|
|
@@ -24,6 +24,9 @@ exports.TRACK_ID = {
|
|
|
24
24
|
music: "track-music",
|
|
25
25
|
text: "track-text",
|
|
26
26
|
caption: "track-caption",
|
|
27
|
+
// Brand-logo overlay lane — a single image clip pinned to a corner, spanning
|
|
28
|
+
// the whole video, painted on top of everything (Reach per-recipient logo).
|
|
29
|
+
logo: "track-logo",
|
|
27
30
|
};
|
|
28
31
|
// ─── strategy → clip kind ────────────────────────────────────────────────
|
|
29
32
|
const STILL_STRATEGIES = new Set([
|
|
@@ -335,6 +335,39 @@ function planToProject(input) {
|
|
|
335
335
|
};
|
|
336
336
|
clips.push(musicClip);
|
|
337
337
|
}
|
|
338
|
+
// Reach per-recipient brand logo — a single contained ImageClip pinned to the
|
|
339
|
+
// top-right corner, spanning the whole video, painted on top of every track.
|
|
340
|
+
// Only emitted when the outreach fan-out injected plan.brandLogoUrl (the
|
|
341
|
+
// planner never sets it), so scene visuals are untouched for every other flow.
|
|
342
|
+
let hasLogo = false;
|
|
343
|
+
if (plan.brandLogoUrl && offsetMs > 0) {
|
|
344
|
+
hasLogo = true;
|
|
345
|
+
// Square box (contain letterboxes any logo aspect — no crop), ~16% of the
|
|
346
|
+
// short side, inset by a ~4%-of-width margin from the top-right corner.
|
|
347
|
+
const side = Math.round(Math.min(size.width, size.height) * 0.16);
|
|
348
|
+
const margin = Math.round(size.width * 0.04);
|
|
349
|
+
const logoClip = {
|
|
350
|
+
id: "brand-logo",
|
|
351
|
+
trackId: planToProject_helpers_1.TRACK_ID.logo,
|
|
352
|
+
type: "image",
|
|
353
|
+
display: { from: 0, to: offsetMs },
|
|
354
|
+
name: "Brand logo",
|
|
355
|
+
src: plan.brandLogoUrl,
|
|
356
|
+
fit: "contain",
|
|
357
|
+
box: {
|
|
358
|
+
left: size.width - side - margin,
|
|
359
|
+
top: margin,
|
|
360
|
+
width: side,
|
|
361
|
+
height: side,
|
|
362
|
+
},
|
|
363
|
+
provenance: { tier: "T0", source: "user-upload", creditsSpent: 0 },
|
|
364
|
+
effects: [],
|
|
365
|
+
transform: { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0, opacity: 1 },
|
|
366
|
+
locked: false,
|
|
367
|
+
metadata: { brandLogo: true },
|
|
368
|
+
};
|
|
369
|
+
clips.push(logoClip);
|
|
370
|
+
}
|
|
338
371
|
// Stage 7 (lock enforcement) — overlay preserved clips verbatim. Runs after
|
|
339
372
|
// ALL clips (incl. music) are built so a preserved clip cleanly displaces
|
|
340
373
|
// its freshly-rebuilt twin by id (ids are deterministic: scene-N-visual /
|
|
@@ -376,6 +409,9 @@ function planToProject(input) {
|
|
|
376
409
|
case planToProject_helpers_1.TRACK_ID.caption:
|
|
377
410
|
hasCaptions = true;
|
|
378
411
|
break;
|
|
412
|
+
case planToProject_helpers_1.TRACK_ID.logo:
|
|
413
|
+
hasLogo = true;
|
|
414
|
+
break;
|
|
379
415
|
default: break; // visual track is always declared
|
|
380
416
|
}
|
|
381
417
|
}
|
|
@@ -400,6 +436,9 @@ function planToProject(input) {
|
|
|
400
436
|
// row per source group, and regen/new captions route back onto it.
|
|
401
437
|
if (hasCaptions)
|
|
402
438
|
tracks.push((0, planToProject_helpers_1.makeTrack)(planToProject_helpers_1.TRACK_ID.caption, "caption", trackIndex++, "Voiceover · Captions", planToProject_helpers_1.TRACK_ID.voiceover));
|
|
439
|
+
// Logo lane is declared LAST → highest track index → painted on top of all.
|
|
440
|
+
if (hasLogo)
|
|
441
|
+
tracks.push((0, planToProject_helpers_1.makeTrack)(planToProject_helpers_1.TRACK_ID.logo, "video", trackIndex++, "Logo"));
|
|
403
442
|
const durationMs = Math.max(1, offsetMs);
|
|
404
443
|
return {
|
|
405
444
|
schemaVersion: project_schema_1.PROJECT_SCHEMA_VERSION,
|
|
@@ -153,7 +153,19 @@ function videoDetails(clip) {
|
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
155
|
function imageDetails(clip) {
|
|
156
|
-
|
|
156
|
+
const d = { src: clip.src };
|
|
157
|
+
// Positioned overlay images (e.g. a corner brand logo) carry an explicit
|
|
158
|
+
// pixel box + fit; the renderer's container/media styles read left/top/
|
|
159
|
+
// width/height/objectFit. Absent → full-frame cover as before.
|
|
160
|
+
if (clip.box) {
|
|
161
|
+
d.left = clip.box.left;
|
|
162
|
+
d.top = clip.box.top;
|
|
163
|
+
d.width = clip.box.width;
|
|
164
|
+
d.height = clip.box.height;
|
|
165
|
+
}
|
|
166
|
+
if (clip.fit)
|
|
167
|
+
d.objectFit = clip.fit;
|
|
168
|
+
return d;
|
|
157
169
|
}
|
|
158
170
|
function audioDetails(clip) {
|
|
159
171
|
return {
|