@vanillaskyai/video 0.7.1 → 0.8.1
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/CHANGELOG.md +63 -0
- package/PUBLIC-API.md +83 -79
- package/README.md +20 -15
- package/dist/assets/vanillasky-logo.svg +151 -0
- package/dist/{builtin-server-KB7FKF6A.js → builtin-server-CUYPIDE7.js} +2 -2
- package/dist/check-runtime.js +1 -1
- package/dist/{chunk-2PYO6VAC.js → chunk-2DOLF4QN.js} +1 -43
- package/dist/{chunk-GAIOHGNR.js → chunk-2OKB27TG.js} +42 -45
- package/dist/{chunk-K746NUIP.js → chunk-2USVB3CY.js} +80 -29
- package/dist/{chunk-RE4IMWJR.js → chunk-3VRMKFM7.js} +87 -14
- package/dist/{chunk-JNN3EYVP.js → chunk-4UNKZEAT.js} +46 -46
- package/dist/{chunk-2XT4MZ76.js → chunk-APQKY6P3.js} +36 -3
- package/dist/{chunk-LLPMVDJZ.js → chunk-L7QGGKJ2.js} +69 -38
- package/dist/{chunk-RGF452LL.js → chunk-OWBT45EN.js} +1 -1
- package/dist/{chunk-LKBZX7GV.js → chunk-PP3MCPN4.js} +4 -4
- package/dist/chunk-ZD2VTUYR.js +28 -0
- package/dist/cli.js +131 -24
- package/dist/{compose-video-PD6LKKRK.js → compose-video-2NFH4DZY.js} +3 -3
- package/dist/{events-B6qS1Lsb.d.ts → events-BQ6z264n.d.ts} +1 -1
- package/dist/index.d.ts +5 -67
- package/dist/index.js +2 -4
- package/dist/{kit-8g46H2RZ.d.ts → kit-tQnCPMOM.d.ts} +1 -1
- package/dist/react.d.ts +33 -53
- package/dist/react.js +982 -648
- package/dist/server.d.ts +11 -13
- package/dist/server.js +170 -81
- package/dist/template-catalog.js +1 -1
- package/dist/templates.d.ts +3 -3
- package/dist/test.d.ts +2 -2
- package/dist/test.js +3 -3
- package/dist/{text-stream-ROTNGSD4.js → text-stream-54LXABJW.js} +1 -1
- package/dist/{types-2wHBqtg8.d.ts → types-CEh9eUFu.d.ts} +1 -1
- package/dist/{types-DrABlRa7.d.ts → types-DWFO6qca.d.ts} +1 -1
- package/docs/agent-integration.md +9 -3
- package/docs/architecture.md +8 -10
- package/docs/concepts.md +16 -40
- package/docs/custom-templates.md +27 -23
- package/docs/customization.md +23 -55
- package/docs/errors.md +58 -68
- package/docs/getting-started.md +21 -14
- package/docs/immersive-interface.md +87 -0
- package/docs/media-and-audio.md +2 -2
- package/docs/performance.md +69 -0
- package/docs/persistence.md +12 -27
- package/docs/production.md +2 -2
- package/docs/provider-integration.md +11 -5
- package/docs/reference/design-system.html +115 -0
- package/docs/reference/provider-adapters.md +37 -89
- package/docs/security.md +1 -1
- package/docs/streaming-protocol.md +2 -2
- package/examples/custom-template/README.md +6 -6
- package/package.json +6 -7
- package/registry/items/barChart.json +2 -2
- package/registry/items/bigNumber.json +2 -2
- package/registry/items/brandMessage.json +2 -2
- package/registry/items/cardList.json +2 -2
- package/registry/items/codeEditor.json +2 -2
- package/registry/items/ctaLogo.json +2 -2
- package/registry/items/ctaMedia.json +2 -2
- package/registry/items/incomingCall.json +2 -2
- package/registry/items/media.json +2 -2
- package/registry/items/milestone.json +2 -2
- package/registry/items/notification.json +2 -2
- package/registry/items/phoneMockup.json +2 -2
- package/registry/items/problemSolution.json +2 -2
- package/registry/items/progressRing.json +2 -2
- package/registry/items/promptInput.json +2 -2
- package/registry/items/reaction.json +2 -2
- package/registry/items/reviewStack.json +2 -2
- package/registry/items/steps.json +2 -2
- package/registry/items/terminal.json +2 -2
- package/registry/items/testimonial.json +2 -2
- package/registry/items/tripleStats.json +2 -2
- package/registry/items/tweet.json +2 -2
- package/registry/items/webMockup.json +2 -2
- package/starters/video-chat/.env.example +4 -2
- package/starters/video-chat/README.md +17 -5
- package/starters/video-chat/index.html +2 -1
- package/starters/video-chat/package.json +2 -4
- package/starters/video-chat/providers/speech.ts +20 -0
- package/starters/video-chat/providers/video.ts +66 -0
- package/starters/video-chat/providers.ts +3 -0
- package/starters/video-chat/server.ts +4 -80
- package/starters/video-chat/stock.ts +114 -99
- package/styles/video-chat.css +650 -246
- package/dist/chunk-SKRGRKHY.js +0 -142
- package/dist/state-DZcKuS32.d.ts +0 -3
- package/docs/branding-and-personalization.md +0 -87
- package/examples/custom-template/supplied-media.tsx +0 -92
|
@@ -3,7 +3,7 @@ function createBackgroundMediaSchemaProperties(options = {}) {
|
|
|
3
3
|
const mediaKeyword = {
|
|
4
4
|
type: "string",
|
|
5
5
|
title: "Background search keyword",
|
|
6
|
-
description: "2-4 word English term for
|
|
6
|
+
description: "2-4 word English term for stock-media search (auto-fills mediaUrl).",
|
|
7
7
|
...options.stockMediaKeyword === false ? {} : { format: "stock-media-keyword" },
|
|
8
8
|
default: ""
|
|
9
9
|
};
|
|
@@ -26,7 +26,7 @@ function createBackgroundMediaSchemaProperties(options = {}) {
|
|
|
26
26
|
mediaPoster: {
|
|
27
27
|
type: "string",
|
|
28
28
|
title: "Background poster image",
|
|
29
|
-
description: "Still image URL shown while a video backdrop is decoding its first frame.
|
|
29
|
+
description: "Still image URL shown while a video backdrop is decoding its first frame. A media resolver can supply a poster alongside a video mediaUrl. Hides the gradient flash that would otherwise appear in the ~50\u2013400ms gap between a <video> mounting and decoding its first frame.",
|
|
30
30
|
format: "uri",
|
|
31
31
|
default: ""
|
|
32
32
|
},
|
|
@@ -84,7 +84,7 @@ var BUILTIN_TEMPLATE_SCHEMAS = {
|
|
|
84
84
|
"reactionTag": {
|
|
85
85
|
"type": "string",
|
|
86
86
|
"title": "Reaction tag",
|
|
87
|
-
"description": "
|
|
87
|
+
"description": "Stock-media search intent. Pick from the punchline/problem copy first, then the overall video topic.",
|
|
88
88
|
"enum": [
|
|
89
89
|
"launch",
|
|
90
90
|
"productLaunch",
|
|
@@ -125,7 +125,7 @@ var BUILTIN_TEMPLATE_SCHEMAS = {
|
|
|
125
125
|
"mediaUrl": {
|
|
126
126
|
"type": "string",
|
|
127
127
|
"title": "Reaction clip",
|
|
128
|
-
"description": "Resolved
|
|
128
|
+
"description": "Resolved stock-video URL.",
|
|
129
129
|
"format": "uri",
|
|
130
130
|
"default": ""
|
|
131
131
|
},
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getReadableSceneDuration
|
|
3
|
+
} from "./chunk-3O7OMMMF.js";
|
|
4
|
+
|
|
5
|
+
// src/protocol/scene-duration.ts
|
|
6
|
+
var NARRATION_WORDS_PER_SECOND = 2.6;
|
|
7
|
+
var NARRATION_TAIL_SECONDS = 0.6;
|
|
8
|
+
function getSceneDurationBounds(scene, metadata) {
|
|
9
|
+
return {
|
|
10
|
+
minimum: Math.max(1e-3, metadata?.minDuration ?? 1),
|
|
11
|
+
readable: getReadableSceneDuration(scene, metadata)
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function getSpokenDuration(narration) {
|
|
15
|
+
const words = narration.trim().split(/\s+/u).filter(Boolean).length;
|
|
16
|
+
if (words === 0) return 0;
|
|
17
|
+
return words / NARRATION_WORDS_PER_SECOND + NARRATION_TAIL_SECONDS;
|
|
18
|
+
}
|
|
19
|
+
function getSceneDuration(scene, metadata) {
|
|
20
|
+
const { readable } = getSceneDurationBounds(scene, metadata);
|
|
21
|
+
return Math.max(readable, scene.narration ? getSpokenDuration(scene.narration) : 0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
getSceneDurationBounds,
|
|
26
|
+
getSpokenDuration,
|
|
27
|
+
getSceneDuration
|
|
28
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -8,14 +8,14 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
getBuiltinTemplateMetadata,
|
|
10
10
|
listBuiltinTemplateMetadata
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-4UNKZEAT.js";
|
|
12
12
|
import "./chunk-3O7OMMMF.js";
|
|
13
13
|
import {
|
|
14
14
|
assertTemplateTransitionMetadata
|
|
15
15
|
} from "./chunk-2E6T633S.js";
|
|
16
16
|
import {
|
|
17
17
|
BUILTIN_TEMPLATE_MANIFEST
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-PP3MCPN4.js";
|
|
19
19
|
import {
|
|
20
20
|
externalVideoBackdropCapabilitySource
|
|
21
21
|
} from "./chunk-WJ6FLKNC.js";
|
|
@@ -1629,6 +1629,10 @@ async function initVideoChatApp(options) {
|
|
|
1629
1629
|
for (const path of SCAFFOLD_FILES) {
|
|
1630
1630
|
planned.set(path, readFileSync2(join8(starterRoot, path), "utf8"));
|
|
1631
1631
|
}
|
|
1632
|
+
const providersPath = safeProjectPath(cwd, "providers.ts");
|
|
1633
|
+
if (!existsSync3(providersPath)) {
|
|
1634
|
+
planned.set("providers.ts", readFileSync2(join8(starterRoot, "providers.ts"), "utf8"));
|
|
1635
|
+
}
|
|
1632
1636
|
planned.set("package.json", mergedManifest(cwd, starterRoot, options.sdkSpec));
|
|
1633
1637
|
planned.set(".gitignore", mergeGitignore(
|
|
1634
1638
|
existsSync3(join8(cwd, ".gitignore")) ? readFileSync2(join8(cwd, ".gitignore"), "utf8") : void 0
|
|
@@ -1651,7 +1655,6 @@ async function initVideoChatApp(options) {
|
|
|
1651
1655
|
}
|
|
1652
1656
|
throw new Error(`${path} already exists with different content. Run init in a new npm project.`);
|
|
1653
1657
|
}
|
|
1654
|
-
if (!changed) return { initialized: false, installed: false };
|
|
1655
1658
|
for (const [path, contents] of planned) {
|
|
1656
1659
|
const destination = safeProjectPath(cwd, path);
|
|
1657
1660
|
if (!existsSync3(destination) || readFileSync2(destination, "utf8") !== contents) atomicWrite(destination, contents);
|
|
@@ -1662,16 +1665,89 @@ async function initVideoChatApp(options) {
|
|
|
1662
1665
|
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
1663
1666
|
throw new Error(`${detail}. The app files are ready; run npm install to finish setup.`);
|
|
1664
1667
|
}
|
|
1665
|
-
return { initialized:
|
|
1668
|
+
return { initialized: changed, installed: true };
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
// src/cli/providers.ts
|
|
1672
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1673
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1674
|
+
import { dirname as dirname4, join as join9 } from "path";
|
|
1675
|
+
var PROVIDERS = {
|
|
1676
|
+
speech: { dependency: "@ai-sdk/xai", version: "^4.0.52", key: "XAI_API_KEY", export: "speechProvider" },
|
|
1677
|
+
video: { dependency: "@fal-ai/client", version: "1.10.1", key: "FAL_KEY", export: "videoProvider" }
|
|
1678
|
+
};
|
|
1679
|
+
function registry(enabled) {
|
|
1680
|
+
const imports = enabled.map((name) => `import { ${PROVIDERS[name].export} } from "./providers/${name}";`).join("\n");
|
|
1681
|
+
const value = enabled.length ? `{
|
|
1682
|
+
${enabled.map((name) => ` ...${PROVIDERS[name].export},`).join("\n")}
|
|
1683
|
+
}` : "{}";
|
|
1684
|
+
return `import type { VideoChatHandlerOptions } from "@vanillaskyai/video/server";
|
|
1685
|
+
${imports ? `${imports}
|
|
1686
|
+
` : ""}
|
|
1687
|
+
export const providers: Pick<VideoChatHandlerOptions, "generateSpeech" | "generateVideo" | "transcribe"> = ${value};
|
|
1688
|
+
`;
|
|
1689
|
+
}
|
|
1690
|
+
async function addVideoChatProvider(name, options) {
|
|
1691
|
+
if (name !== "speech" && name !== "video") throw new Error("Choose a provider capability: speech or video.");
|
|
1692
|
+
const manifestPath = safeProjectPath(options.cwd, "package.json");
|
|
1693
|
+
const registryPath = safeProjectPath(options.cwd, "providers.ts");
|
|
1694
|
+
if (!existsSync4(manifestPath) || !existsSync4(registryPath)) throw new Error("Run vanillasky init before adding providers.");
|
|
1695
|
+
const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
|
|
1696
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) throw new Error("Expected an application package.json object.");
|
|
1697
|
+
const config = manifest.vanillasky ?? {};
|
|
1698
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) throw new Error("Expected vanillasky configuration to be an object.");
|
|
1699
|
+
const previous = config.providers ?? [];
|
|
1700
|
+
if (!Array.isArray(previous) || previous.some((value) => value !== "speech" && value !== "video") || new Set(previous).size !== previous.length) {
|
|
1701
|
+
throw new Error("Invalid vanillasky.providers configuration.");
|
|
1702
|
+
}
|
|
1703
|
+
const enabled = [...previous].sort();
|
|
1704
|
+
if (readFileSync3(registryPath, "utf8") !== registry(enabled)) {
|
|
1705
|
+
throw new Error("providers.ts has custom wiring. Preserve it and add the provider manually, or restore the generated wiring before using this command.");
|
|
1706
|
+
}
|
|
1707
|
+
const adapterPath = safeProjectPath(options.cwd, `providers/${name}.ts`);
|
|
1708
|
+
const adapter = readFileSync3(join9(options.starterRoot ?? locateStarterRoot(), "providers", `${name}.ts`), "utf8");
|
|
1709
|
+
if (!enabled.includes(name) && existsSync4(adapterPath) && readFileSync3(adapterPath, "utf8") !== adapter) {
|
|
1710
|
+
throw new Error(`providers/${name}.ts already contains customer source; no files were changed.`);
|
|
1711
|
+
}
|
|
1712
|
+
const dependencies = manifest.dependencies ?? {};
|
|
1713
|
+
if (!dependencies || typeof dependencies !== "object" || Array.isArray(dependencies)) throw new Error("Expected dependencies to be an object.");
|
|
1714
|
+
if (!enabled.includes(name)) enabled.push(name);
|
|
1715
|
+
enabled.sort();
|
|
1716
|
+
const provider = PROVIDERS[name];
|
|
1717
|
+
manifest.dependencies = { ...dependencies, [provider.dependency]: dependencies[provider.dependency] ?? provider.version };
|
|
1718
|
+
manifest.vanillasky = { ...config, providers: enabled };
|
|
1719
|
+
const writes = /* @__PURE__ */ new Map([
|
|
1720
|
+
[manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
1721
|
+
`],
|
|
1722
|
+
[registryPath, registry(enabled)]
|
|
1723
|
+
]);
|
|
1724
|
+
if (!existsSync4(adapterPath)) writes.set(adapterPath, adapter);
|
|
1725
|
+
const staged = [...writes].map(([path, contents]) => ({ path, contents, temporary: safeProjectPath(options.cwd, `${path.slice(options.cwd.length + 1)}.${randomUUID3()}.tmp`) }));
|
|
1726
|
+
for (const { path, contents, temporary } of staged) {
|
|
1727
|
+
mkdirSync3(dirname4(path), { recursive: true });
|
|
1728
|
+
writeFileSync3(temporary, contents, { flag: "wx" });
|
|
1729
|
+
try {
|
|
1730
|
+
renameSync3(temporary, path);
|
|
1731
|
+
} finally {
|
|
1732
|
+
if (existsSync4(temporary)) unlinkSync3(temporary);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
try {
|
|
1736
|
+
await (options.installDependencies ?? defaultInstall)(options.cwd);
|
|
1737
|
+
} catch {
|
|
1738
|
+
throw new Error(`Provider files are ready. Rerun npx vanillasky providers add ${name} to finish installation.`);
|
|
1739
|
+
}
|
|
1740
|
+
return `Enabled ${name}. Add ${provider.key} to .env.local, then restart npm run dev.`;
|
|
1666
1741
|
}
|
|
1667
1742
|
|
|
1668
1743
|
// src/cli/doctor.ts
|
|
1669
|
-
import { existsSync as
|
|
1670
|
-
import { join as
|
|
1744
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1745
|
+
import { join as join10, resolve as resolve8 } from "path";
|
|
1671
1746
|
var REQUIRED_FILES = [
|
|
1672
1747
|
".env.local",
|
|
1673
1748
|
"index.html",
|
|
1674
1749
|
"server.ts",
|
|
1750
|
+
"providers.ts",
|
|
1675
1751
|
"src/main.tsx",
|
|
1676
1752
|
"stock.ts",
|
|
1677
1753
|
"tsconfig.json",
|
|
@@ -1680,11 +1756,15 @@ var REQUIRED_FILES = [
|
|
|
1680
1756
|
var REQUIRED_DEPENDENCIES = [
|
|
1681
1757
|
"@vanillaskyai/video",
|
|
1682
1758
|
"@ai-sdk/anthropic",
|
|
1683
|
-
"@ai-sdk/xai",
|
|
1684
|
-
"@fal-ai/client",
|
|
1685
1759
|
"ai",
|
|
1686
1760
|
"react",
|
|
1687
|
-
"react-dom"
|
|
1761
|
+
"react-dom",
|
|
1762
|
+
"@types/node",
|
|
1763
|
+
"@types/react",
|
|
1764
|
+
"@types/react-dom",
|
|
1765
|
+
"@vitejs/plugin-react",
|
|
1766
|
+
"typescript",
|
|
1767
|
+
"vite"
|
|
1688
1768
|
];
|
|
1689
1769
|
var PROVIDER_KEYS = /* @__PURE__ */ new Set(["ANTHROPIC_API_KEY", "XAI_API_KEY", "FAL_KEY", "PEXELS_API_KEY"]);
|
|
1690
1770
|
var CLIENT_ENV_PREFIXES = ["VITE", "NEXT_PUBLIC"].map((prefix) => `${prefix}_`);
|
|
@@ -1717,14 +1797,14 @@ function doctorVideoChatApp(cwdInput) {
|
|
|
1717
1797
|
const lines = [];
|
|
1718
1798
|
let ok = true;
|
|
1719
1799
|
for (const path of REQUIRED_FILES) {
|
|
1720
|
-
if (!
|
|
1800
|
+
if (!existsSync5(join10(cwd, path))) {
|
|
1721
1801
|
ok = false;
|
|
1722
1802
|
lines.push(`MISSING ${path}`);
|
|
1723
1803
|
}
|
|
1724
1804
|
}
|
|
1725
1805
|
let manifest = {};
|
|
1726
1806
|
try {
|
|
1727
|
-
const parsed = JSON.parse(
|
|
1807
|
+
const parsed = JSON.parse(readFileSync4(join10(cwd, "package.json"), "utf8"));
|
|
1728
1808
|
if (!isJsonObject(parsed)) throw new Error("expected an object");
|
|
1729
1809
|
manifest = parsed;
|
|
1730
1810
|
} catch {
|
|
@@ -1737,17 +1817,22 @@ function doctorVideoChatApp(cwdInput) {
|
|
|
1737
1817
|
ok = false;
|
|
1738
1818
|
lines.push(`MISSING dependency ${name}`);
|
|
1739
1819
|
}
|
|
1820
|
+
const installed = (name) => existsSync5(join10(cwd, "node_modules", name, "package.json"));
|
|
1821
|
+
for (const name of REQUIRED_DEPENDENCIES.filter((name2) => dependencies[name2] && !installed(name2))) {
|
|
1822
|
+
ok = false;
|
|
1823
|
+
lines.push(`NOT INSTALLED ${name} \u2014 run npm install`);
|
|
1824
|
+
}
|
|
1740
1825
|
const scripts = stringMap2(manifest.scripts);
|
|
1741
1826
|
if (scripts.dev !== "vite" || scripts.build !== "tsc && vite build") {
|
|
1742
1827
|
ok = false;
|
|
1743
1828
|
lines.push("MISSING video-chat dev/build scripts");
|
|
1744
1829
|
}
|
|
1745
|
-
const ignore =
|
|
1830
|
+
const ignore = existsSync5(join10(cwd, ".gitignore")) ? readFileSync4(join10(cwd, ".gitignore"), "utf8") : "";
|
|
1746
1831
|
if (!effectivelyIgnoresLocalEnvironment(ignore)) {
|
|
1747
1832
|
ok = false;
|
|
1748
1833
|
lines.push("UNSAFE .env.local is not ignored");
|
|
1749
1834
|
}
|
|
1750
|
-
const environment =
|
|
1835
|
+
const environment = existsSync5(join10(cwd, ".env.local")) ? parseEnvironment(readFileSync4(join10(cwd, ".env.local"), "utf8")) : /* @__PURE__ */ new Map();
|
|
1751
1836
|
for (const name of environment.keys()) {
|
|
1752
1837
|
if (CLIENT_ENV_PREFIXES.some((prefix) => name.startsWith(prefix) && PROVIDER_KEYS.has(name.slice(prefix.length)))) {
|
|
1753
1838
|
ok = false;
|
|
@@ -1760,10 +1845,23 @@ function doctorVideoChatApp(cwdInput) {
|
|
|
1760
1845
|
ok = false;
|
|
1761
1846
|
lines.push("MISSING ANTHROPIC_API_KEY in .env.local");
|
|
1762
1847
|
}
|
|
1763
|
-
|
|
1764
|
-
|
|
1848
|
+
const configuration = isJsonObject(manifest.vanillasky) ? manifest.vanillasky : {};
|
|
1849
|
+
const enabled = Array.isArray(configuration.providers) ? configuration.providers : [];
|
|
1850
|
+
for (const provider of [
|
|
1851
|
+
{ id: "speech", label: "generated speech", dependency: "@ai-sdk/xai", key: "XAI_API_KEY" },
|
|
1852
|
+
{ id: "video", label: "generated video + transcription", dependency: "@fal-ai/client", key: "FAL_KEY" }
|
|
1853
|
+
]) {
|
|
1854
|
+
const configured = enabled.includes(provider.id) && existsSync5(join10(cwd, "providers.ts")) && existsSync5(join10(cwd, "providers", `${provider.id}.ts`)) && dependencies[provider.dependency] && installed(provider.dependency);
|
|
1855
|
+
if (!configured) {
|
|
1856
|
+
lines.push(`OPTIONAL ${provider.label} \u2014 run npx vanillasky providers add ${provider.id}`);
|
|
1857
|
+
} else if (!environment.get(provider.key)) {
|
|
1858
|
+
lines.push(`OPTIONAL ${provider.label} \u2014 add ${provider.key} to .env.local`);
|
|
1859
|
+
} else {
|
|
1860
|
+
lines.push(`READY ${provider.label}`);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1765
1863
|
lines.push(environment.get("PEXELS_API_KEY") ? "READY stock media" : "OPTIONAL stock media \u2014 add PEXELS_API_KEY");
|
|
1766
|
-
if (
|
|
1864
|
+
if (existsSync5(join10(cwd, "vanillasky", "templates"))) {
|
|
1767
1865
|
lines.push("CHECK custom templates with npx vanillasky templates check");
|
|
1768
1866
|
}
|
|
1769
1867
|
return { ok, lines };
|
|
@@ -1777,6 +1875,7 @@ function help() {
|
|
|
1777
1875
|
"Usage:",
|
|
1778
1876
|
" vanillasky init",
|
|
1779
1877
|
" vanillasky doctor",
|
|
1878
|
+
" vanillasky providers add <speech|video>",
|
|
1780
1879
|
" vanillasky templates <command>",
|
|
1781
1880
|
"",
|
|
1782
1881
|
"Template commands:",
|
|
@@ -1848,14 +1947,22 @@ function runVanillaSkyCli(argv, environment = {}) {
|
|
|
1848
1947
|
sdkSpec: environment.sdkSpec ?? process.env.npm_config_package,
|
|
1849
1948
|
installDependencies: environment.installDependencies
|
|
1850
1949
|
});
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
write(
|
|
1858
|
-
|
|
1950
|
+
write(result.initialized ? "Video chat initialized with packaged templates and browser voice." : "Video chat is already initialized; dependencies checked.");
|
|
1951
|
+
const health = doctorVideoChatApp(cwd);
|
|
1952
|
+
health.lines.forEach(write);
|
|
1953
|
+
write(health.ok ? "Ready. Run: npm run dev" : health.lines.some((line) => line === "MISSING ANTHROPIC_API_KEY in .env.local") ? "Add ANTHROPIC_API_KEY to .env.local. Then run: npm run dev" : "Next: fix the missing setup items above, then run npm run dev.");
|
|
1954
|
+
return 0;
|
|
1955
|
+
} catch (error) {
|
|
1956
|
+
write(error instanceof Error ? error.message : String(error));
|
|
1957
|
+
return 1;
|
|
1958
|
+
}
|
|
1959
|
+
})();
|
|
1960
|
+
}
|
|
1961
|
+
if (rootCommand === "providers") {
|
|
1962
|
+
return (async () => {
|
|
1963
|
+
try {
|
|
1964
|
+
if (rootArgs.length !== 2 || rootArgs[0] !== "add") throw new Error("Usage: vanillasky providers add <speech|video>");
|
|
1965
|
+
write(await addVideoChatProvider(rootArgs[1], { cwd, starterRoot: environment.starterRoot, installDependencies: environment.installDependencies }));
|
|
1859
1966
|
return 0;
|
|
1860
1967
|
} catch (error) {
|
|
1861
1968
|
write(error instanceof Error ? error.message : String(error));
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createVideo
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-3VRMKFM7.js";
|
|
4
4
|
import "./chunk-E7CL7UPB.js";
|
|
5
|
-
import "./chunk-
|
|
5
|
+
import "./chunk-2OKB27TG.js";
|
|
6
6
|
import "./chunk-MMUXVA47.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-2DOLF4QN.js";
|
|
8
8
|
import "./chunk-AOWSXU2K.js";
|
|
9
9
|
import "./chunk-3O7OMMMF.js";
|
|
10
10
|
import "./chunk-XGRA2MUV.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as VideoOrientation, V as VideoStyle, c as Video, a as VideoCapabilities, d as VideoAudio, e as VideoScene, f as VIDEO_PROTOCOL_VERSION } from './types-
|
|
1
|
+
import { b as VideoOrientation, V as VideoStyle, c as Video, a as VideoCapabilities, d as VideoAudio, e as VideoScene, f as VIDEO_PROTOCOL_VERSION } from './types-CEh9eUFu.js';
|
|
2
2
|
|
|
3
3
|
type VideoWarningCategory = "validation" | "readability" | "grounding" | "provider" | "media" | "protocol";
|
|
4
4
|
type VideoWarningCode = "scene_duration_adjusted" | "scene_omitted_unreadable" | "scene_omitted_for_closer" | "scene_variable_clipped" | "chart_scale_imbalance" | "plan_incomplete" | "plan_missing_closer" | "provider_warning" | "provider_diagnostics_unavailable" | "media_budget_reached";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { c as Video, h as VideoBrandInput, i as VideoBrand, e as VideoScene,
|
|
2
|
-
export {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { c as Video, h as VideoBrandInput, i as VideoBrand, e as VideoScene, j as VideoTemplatePacing } from './types-CEh9eUFu.js';
|
|
2
|
+
export { d as VideoAudio, k as VideoBackground, b as VideoOrientation, V as VideoStyle, l as VideoStyleOptions } from './types-CEh9eUFu.js';
|
|
3
|
+
|
|
4
|
+
type VideoStatus = "idle" | "streaming" | "complete" | "error" | "aborted";
|
|
5
5
|
|
|
6
6
|
type VideoValidationErrorCode = "invalid_video" | "unsupported_video_version";
|
|
7
7
|
declare class VideoValidationError extends Error {
|
|
@@ -15,68 +15,6 @@ declare function getVideoDuration(config: Video): number;
|
|
|
15
15
|
|
|
16
16
|
declare function resolveVideoBrand(input?: VideoBrandInput): VideoBrand;
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* Compose a playable video from scenes the host builds itself.
|
|
20
|
-
*
|
|
21
|
-
* `VideoInput.opening` is a single line of copy, so an application that wants
|
|
22
|
-
* several scenes of its own before the generated ones has nowhere to put them.
|
|
23
|
-
* Handing the player a fresh `video` prop is not an answer either: that resets
|
|
24
|
-
* playback to zero on every change, so a video that grows replays its opening
|
|
25
|
-
* each time something is appended. The player's `stream` prop appends, and this
|
|
26
|
-
* builds a stream.
|
|
27
|
-
*
|
|
28
|
-
* The reason it belongs here rather than in each application is that the
|
|
29
|
-
* envelope rules are exact and their failure is silent. `response.start` must
|
|
30
|
-
* be sequence zero, every event exactly one higher, `eventId` exactly
|
|
31
|
-
* `${runId}:${sequence}`, `scene.add.position` the index the scene lands at,
|
|
32
|
-
* and the completion snapshot must reproduce what the reducer built. Miss any
|
|
33
|
-
* one and the whole stream is rejected: the player renders nothing, and the
|
|
34
|
-
* console stays clean.
|
|
35
|
-
*/
|
|
36
|
-
interface SceneTimeline {
|
|
37
|
-
/**
|
|
38
|
-
* Hand this to the player's `stream` prop once. Never swap it: a new stream
|
|
39
|
-
* is a new video, and playback restarts.
|
|
40
|
-
*/
|
|
41
|
-
stream: AsyncIterable<VideoEvent>;
|
|
42
|
-
/** Append a scene. Ignored once the timeline has completed. */
|
|
43
|
-
add(scene: VideoScene): void;
|
|
44
|
-
/**
|
|
45
|
-
* Say what the soundtrack is, or that there is none.
|
|
46
|
-
*
|
|
47
|
-
* Only meaningful with `awaitAudio`, where it releases the scenes held back
|
|
48
|
-
* while the track was unknown. Calling it twice, or after the first scene has
|
|
49
|
-
* been emitted, does nothing.
|
|
50
|
-
*/
|
|
51
|
-
setAudio(audio: VideoAudio | undefined): void;
|
|
52
|
-
/** Finish the video. Safe to call more than once. */
|
|
53
|
-
complete(): void;
|
|
54
|
-
}
|
|
55
|
-
interface SceneTimelineOptions {
|
|
56
|
-
style: VideoStyle;
|
|
57
|
-
orientation?: VideoOrientation;
|
|
58
|
-
/**
|
|
59
|
-
* The soundtrack, when it is already known.
|
|
60
|
-
*
|
|
61
|
-
* Use `awaitAudio` instead when it is resolved by a request that has not
|
|
62
|
-
* answered yet.
|
|
63
|
-
*/
|
|
64
|
-
audio?: VideoAudio;
|
|
65
|
-
/**
|
|
66
|
-
* Hold scenes until `setAudio` is called.
|
|
67
|
-
*
|
|
68
|
-
* `audio.set` is only valid before the first scene, so a timeline that opens
|
|
69
|
-
* with host-built scenes cannot add a soundtrack afterwards - the event
|
|
70
|
-
* arrives too late and the video plays silently, with nothing to show for it.
|
|
71
|
-
* Holding the openings costs the wait for the track to resolve, not the wait
|
|
72
|
-
* for the video to be planned.
|
|
73
|
-
*/
|
|
74
|
-
awaitAudio?: boolean;
|
|
75
|
-
requestId?: string;
|
|
76
|
-
runId?: string;
|
|
77
|
-
}
|
|
78
|
-
declare function createSceneTimeline(options: SceneTimelineOptions): SceneTimeline;
|
|
79
|
-
|
|
80
18
|
interface SceneDurationBounds {
|
|
81
19
|
/**
|
|
82
20
|
* The least this scene survives being shown for.
|
|
@@ -100,4 +38,4 @@ declare function getSceneDurationBounds(scene: Pick<VideoScene, "variables">, me
|
|
|
100
38
|
declare function getSpokenDuration(narration: string): number;
|
|
101
39
|
declare function getSceneDuration(scene: Pick<VideoScene, "variables" | "narration">, metadata: VideoTemplatePacing | undefined): number;
|
|
102
40
|
|
|
103
|
-
export { type SceneDurationBounds,
|
|
41
|
+
export { type SceneDurationBounds, Video, VideoBrand, VideoBrandInput, VideoScene, type VideoStatus, VideoValidationError, type VideoValidationErrorCode, getSceneDuration, getSceneDurationBounds, getSpokenDuration, getVideoDuration, parseVideo, resolveVideoBrand };
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createSceneTimeline,
|
|
3
2
|
getSceneDuration,
|
|
4
3
|
getSceneDurationBounds,
|
|
5
4
|
getSpokenDuration
|
|
6
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ZD2VTUYR.js";
|
|
7
6
|
import {
|
|
8
7
|
VideoValidationError,
|
|
9
8
|
parseVideo
|
|
10
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-2DOLF4QN.js";
|
|
11
10
|
import "./chunk-AOWSXU2K.js";
|
|
12
11
|
import "./chunk-3O7OMMMF.js";
|
|
13
12
|
import {
|
|
@@ -18,7 +17,6 @@ import {
|
|
|
18
17
|
} from "./chunk-XGRA2MUV.js";
|
|
19
18
|
export {
|
|
20
19
|
VideoValidationError,
|
|
21
|
-
createSceneTimeline,
|
|
22
20
|
getSceneDuration,
|
|
23
21
|
getSceneDurationBounds,
|
|
24
22
|
getSpokenDuration,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { V as VideoStyle, a as VideoCapabilities } from './types-
|
|
1
|
+
import { V as VideoStyle, a as VideoCapabilities } from './types-CEh9eUFu.js';
|
|
2
2
|
import { ComponentType } from 'react';
|
|
3
3
|
import { S as SceneTemplateMetadata, d as TemplateJsonSchema, f as TemplateJsonSchemaProperty } from './catalog-types-BIhSpOWK.js';
|
|
4
4
|
|
package/dist/react.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { CSSProperties, ReactElement, ReactNode } from 'react';
|
|
3
|
-
import { b as VideoEvent
|
|
4
|
-
import { b as VideoOrientation, c as Video, e as VideoScene,
|
|
5
|
-
import { T as TemplateRegistry } from './kit-
|
|
6
|
-
import { V as
|
|
7
|
-
import { c as VideoChatMode, e as VideoChatAskOptions, f as VideoChatMedia, g as VideoChatSuggestion, a as VideoChatCapabilities, h as VideoChatWelcome } from './types-DrABlRa7.js';
|
|
3
|
+
import { b as VideoEvent } from './events-BQ6z264n.js';
|
|
4
|
+
import { b as VideoOrientation, c as Video, e as VideoScene, h as VideoBrandInput, l as VideoStyleOptions } from './types-CEh9eUFu.js';
|
|
5
|
+
import { T as TemplateRegistry } from './kit-tQnCPMOM.js';
|
|
6
|
+
import { V as VideoChatMode, a as VideoChatAskOptions, b as VideoChatMedia, c as VideoChatSuggestion, d as VideoChatCapabilities, e as VideoChatWelcome } from './types-DWFO6qca.js';
|
|
8
7
|
import './catalog-types-BIhSpOWK.js';
|
|
9
8
|
|
|
10
9
|
type VideoPlaybackMode = "manual" | "muted-autoplay" | "autoplay-with-sound" | "autoplay-after-interaction";
|
|
@@ -62,6 +61,10 @@ interface VideoPlayerSharedProps {
|
|
|
62
61
|
/** Fires when a streamed response has finished composing. */
|
|
63
62
|
onComplete?: (video: Video) => void;
|
|
64
63
|
onError?: (error: Error) => void;
|
|
64
|
+
/** First active scene committed, observed at the next animation frame; excludes idle posters. */
|
|
65
|
+
onFramePresented?: () => unknown;
|
|
66
|
+
/** Stream playback has reached its available scenes; excludes initial waiting and deliberate pauses. */
|
|
67
|
+
onStallChange?: (stalled: boolean) => unknown;
|
|
65
68
|
/** Fires when the scene under the playhead changes, including on a loop wrap. */
|
|
66
69
|
onSceneChange?: (scene: VideoScene, index: number) => void;
|
|
67
70
|
}
|
|
@@ -94,33 +97,6 @@ declare class VideoError extends Error {
|
|
|
94
97
|
constructor(message: string, options: VideoErrorOptions);
|
|
95
98
|
}
|
|
96
99
|
|
|
97
|
-
interface UseVideoOptions {
|
|
98
|
-
endpoint?: string | URL;
|
|
99
|
-
/** Customer-owned templates that replace matching built-ins and add new IDs. */
|
|
100
|
-
templates?: TemplateRegistry;
|
|
101
|
-
/** Templates the planner may select for this client experience. Renderers remain available for replay. */
|
|
102
|
-
templateIds?: readonly string[];
|
|
103
|
-
headers?: HeadersInit;
|
|
104
|
-
credentials?: RequestCredentials;
|
|
105
|
-
fetcher?: typeof fetch;
|
|
106
|
-
/** Override ID generation for tracing or deterministic tests. */
|
|
107
|
-
createRequestId?: () => string;
|
|
108
|
-
}
|
|
109
|
-
interface UseVideoResult {
|
|
110
|
-
/** Resolves with a completed video; rejects terminal errors and aborts. */
|
|
111
|
-
generate(input: VideoInput): Promise<Video>;
|
|
112
|
-
abort(reason?: string): void;
|
|
113
|
-
video?: Video;
|
|
114
|
-
status: VideoStatus;
|
|
115
|
-
error?: VideoError;
|
|
116
|
-
warnings: readonly VideoWarning[];
|
|
117
|
-
/** Spread into an explicitly rendered VideoPlayer. */
|
|
118
|
-
playerProps: VideoPlayerProps & {
|
|
119
|
-
video?: never;
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
declare function useVideo(options?: UseVideoOptions): UseVideoResult;
|
|
123
|
-
|
|
124
100
|
/**
|
|
125
101
|
* Say a video's narration as it plays.
|
|
126
102
|
*
|
|
@@ -143,31 +119,14 @@ interface NarrationVoice {
|
|
|
143
119
|
*
|
|
144
120
|
* The signal aborts when the scene changes, when the viewer interrupts, or
|
|
145
121
|
* when the player goes away. Stop speaking promptly: talking over the next
|
|
146
|
-
* scene is worse than being cut off.
|
|
122
|
+
* scene is worse than being cut off. Report onStart only when speech actually
|
|
123
|
+
* begins playing; leave it uncalled when onset cannot be observed.
|
|
147
124
|
*/
|
|
148
125
|
speak(text: string, options: {
|
|
149
126
|
signal: AbortSignal;
|
|
127
|
+
onStart?: (source?: "browser" | "generated") => void;
|
|
150
128
|
}): void | Promise<void>;
|
|
151
129
|
}
|
|
152
|
-
interface NarrationOptions {
|
|
153
|
-
voice: NarrationVoice;
|
|
154
|
-
/** Say nothing at all while false. Defaults to true. */
|
|
155
|
-
enabled?: boolean;
|
|
156
|
-
}
|
|
157
|
-
interface Narration {
|
|
158
|
-
/**
|
|
159
|
-
* Hand this to the player's `onSceneChange`.
|
|
160
|
-
*
|
|
161
|
-
* The player already reports which scene is showing, which is the only cue a
|
|
162
|
-
* narrator needs, so nothing here has to run its own clock.
|
|
163
|
-
*/
|
|
164
|
-
onSceneChange: (scene: VideoScene, index: number) => void;
|
|
165
|
-
/** Stop the current line. Nothing is said again until the next scene. */
|
|
166
|
-
interrupt: () => void;
|
|
167
|
-
/** Whether a line is being spoken right now. */
|
|
168
|
-
speaking: boolean;
|
|
169
|
-
}
|
|
170
|
-
declare function useNarration(options: NarrationOptions): Narration;
|
|
171
130
|
|
|
172
131
|
interface VideoChatPreparedSpeech {
|
|
173
132
|
/** Measured or conservatively estimated spoken duration, in seconds. */
|
|
@@ -188,6 +147,8 @@ interface CreateVideoChatVoiceOptions {
|
|
|
188
147
|
credentials?: RequestCredentials;
|
|
189
148
|
fetcher?: typeof fetch;
|
|
190
149
|
maxCachedLines?: number;
|
|
150
|
+
/** Called when optional generated speech falls back to browser speech. */
|
|
151
|
+
onFallback?: () => unknown;
|
|
191
152
|
}
|
|
192
153
|
/**
|
|
193
154
|
* Create the SDK's generated-speech client with a browser-voice fallback.
|
|
@@ -204,6 +165,8 @@ interface VideoChatTurn {
|
|
|
204
165
|
/** True only after the complete response has been received. */
|
|
205
166
|
completed: boolean;
|
|
206
167
|
opening?: string;
|
|
168
|
+
/** Concise notices for recovered optional failures. */
|
|
169
|
+
warnings?: readonly string[];
|
|
207
170
|
/** Optional stock footage held behind the opening hook until playback starts. */
|
|
208
171
|
openingMedia?: VideoChatMedia;
|
|
209
172
|
orientation: VideoOrientation;
|
|
@@ -231,7 +194,23 @@ interface UseVideoChatOptions {
|
|
|
231
194
|
createTurnId?: () => string;
|
|
232
195
|
/** Observe how long a fresh response takes to display its first actual scene. */
|
|
233
196
|
onFirstFrame?: (metric: VideoChatFirstFrameMetric) => unknown;
|
|
197
|
+
/** Local observations only; no automatic telemetry or prompt/provider data. */
|
|
198
|
+
onPlaybackMetric?: (metric: VideoChatPlaybackMetric) => unknown;
|
|
234
199
|
}
|
|
200
|
+
type VideoChatPlaybackMetric = {
|
|
201
|
+
turnId: string;
|
|
202
|
+
mode: VideoChatMode;
|
|
203
|
+
elapsedMs: number;
|
|
204
|
+
} & ({
|
|
205
|
+
type: "first-frame";
|
|
206
|
+
} | {
|
|
207
|
+
type: "first-speech";
|
|
208
|
+
source: "browser" | "generated" | "custom";
|
|
209
|
+
} | {
|
|
210
|
+
type: "stall";
|
|
211
|
+
durationMs: number;
|
|
212
|
+
reason: "scene-generation";
|
|
213
|
+
});
|
|
235
214
|
interface VideoChatFirstFrameMetric {
|
|
236
215
|
turnId: string;
|
|
237
216
|
mode: VideoChatMode;
|
|
@@ -252,6 +231,7 @@ interface UseVideoChatResult {
|
|
|
252
231
|
welcome?: VideoChatWelcome;
|
|
253
232
|
availableModes: readonly VideoChatMode[];
|
|
254
233
|
status: VideoChatStatus;
|
|
234
|
+
warnings: readonly string[];
|
|
255
235
|
error?: VideoError;
|
|
256
236
|
suggestions: readonly VideoChatSuggestion[];
|
|
257
237
|
caption?: string;
|
|
@@ -280,4 +260,4 @@ interface VideoChatProps {
|
|
|
280
260
|
/** A complete voice-and-video chat interface backed by createVideoChatHandler. */
|
|
281
261
|
declare function VideoChat({ options, className, welcomeTitle }: VideoChatProps): react.JSX.Element;
|
|
282
262
|
|
|
283
|
-
export { type CreateVideoChatVoiceOptions, type
|
|
263
|
+
export { type CreateVideoChatVoiceOptions, type UseVideoChatOptions, type UseVideoChatResult, VideoChat, VideoChatAskOptions, VideoChatCapabilities, type VideoChatFirstFrameMetric, VideoChatMedia, VideoChatMode, type VideoChatPlaybackMetric, type VideoChatPreparedSpeech, type VideoChatProps, type VideoChatStatus, VideoChatSuggestion, type VideoChatTurn, type VideoChatVoice, VideoChatWelcome, VideoError, type VideoErrorOptions, type VideoPlaybackMode, VideoPlayer, type VideoPlayerProps, createVideoChatVoice, useVideoChat };
|