varg.ai-sdk 0.1.1 → 0.4.0-alpha.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.
Files changed (246) hide show
  1. package/.claude/settings.local.json +1 -1
  2. package/.env.example +3 -0
  3. package/.github/workflows/ci.yml +23 -0
  4. package/.husky/README.md +102 -0
  5. package/.husky/commit-msg +6 -0
  6. package/.husky/pre-commit +9 -0
  7. package/.husky/pre-push +6 -0
  8. package/.size-limit.json +8 -0
  9. package/.test-hooks.ts +5 -0
  10. package/CLAUDE.md +10 -3
  11. package/CONTRIBUTING.md +150 -0
  12. package/LICENSE.md +53 -0
  13. package/README.md +56 -209
  14. package/SKILLS.md +26 -10
  15. package/biome.json +7 -1
  16. package/bun.lock +1286 -0
  17. package/commitlint.config.js +22 -0
  18. package/docs/index.html +1130 -0
  19. package/docs/prompting.md +326 -0
  20. package/docs/react.md +834 -0
  21. package/docs/sdk.md +812 -0
  22. package/ffmpeg/CLAUDE.md +68 -0
  23. package/package.json +43 -10
  24. package/pipeline/cookbooks/scripts/animate-frames-parallel.ts +84 -0
  25. package/pipeline/cookbooks/scripts/combine-scenes.sh +53 -0
  26. package/pipeline/cookbooks/scripts/generate-frames-parallel.ts +99 -0
  27. package/pipeline/cookbooks/scripts/still-to-video.sh +37 -0
  28. package/pipeline/cookbooks/text-to-tiktok.md +669 -0
  29. package/pipeline/cookbooks/trendwatching.md +156 -0
  30. package/plan.md +281 -0
  31. package/scripts/.gitkeep +0 -0
  32. package/src/ai-sdk/cache.ts +142 -0
  33. package/src/ai-sdk/examples/cached-generation.ts +53 -0
  34. package/src/ai-sdk/examples/duet-scene-4.ts +53 -0
  35. package/src/ai-sdk/examples/duet-scene-5-audio.ts +32 -0
  36. package/src/ai-sdk/examples/duet-video.ts +56 -0
  37. package/src/ai-sdk/examples/editly-composition.ts +63 -0
  38. package/src/ai-sdk/examples/editly-test.ts +57 -0
  39. package/src/ai-sdk/examples/editly-video-test.ts +52 -0
  40. package/src/ai-sdk/examples/fal-lipsync.ts +43 -0
  41. package/src/ai-sdk/examples/higgsfield-image.ts +61 -0
  42. package/src/ai-sdk/examples/music-generation.ts +19 -0
  43. package/src/ai-sdk/examples/openai-sora.ts +34 -0
  44. package/src/ai-sdk/examples/replicate-bg-removal.ts +52 -0
  45. package/src/ai-sdk/examples/simpsons-scene.ts +61 -0
  46. package/src/ai-sdk/examples/talking-lion.ts +55 -0
  47. package/src/ai-sdk/examples/video-generation.ts +39 -0
  48. package/src/ai-sdk/examples/workflow-animated-girl.ts +104 -0
  49. package/src/ai-sdk/examples/workflow-before-after.ts +114 -0
  50. package/src/ai-sdk/examples/workflow-character-grid.ts +112 -0
  51. package/src/ai-sdk/examples/workflow-slideshow.ts +161 -0
  52. package/src/ai-sdk/file-cache.ts +112 -0
  53. package/src/ai-sdk/file.ts +238 -0
  54. package/src/ai-sdk/generate-element.ts +92 -0
  55. package/src/ai-sdk/generate-music.ts +46 -0
  56. package/src/ai-sdk/generate-video.ts +165 -0
  57. package/src/ai-sdk/index.ts +72 -0
  58. package/src/ai-sdk/music-model.ts +110 -0
  59. package/src/ai-sdk/providers/editly/editly.test.ts +1108 -0
  60. package/src/ai-sdk/providers/editly/ffmpeg.ts +60 -0
  61. package/src/ai-sdk/providers/editly/index.ts +817 -0
  62. package/src/ai-sdk/providers/editly/layers.ts +776 -0
  63. package/src/ai-sdk/providers/editly/plan.md +144 -0
  64. package/src/ai-sdk/providers/editly/types.ts +328 -0
  65. package/src/ai-sdk/providers/elevenlabs-provider.ts +255 -0
  66. package/src/ai-sdk/providers/fal-provider.ts +512 -0
  67. package/src/ai-sdk/providers/higgsfield.ts +379 -0
  68. package/src/ai-sdk/providers/openai.ts +251 -0
  69. package/src/ai-sdk/providers/replicate.ts +16 -0
  70. package/src/ai-sdk/video-model.ts +185 -0
  71. package/src/cli/commands/find.tsx +137 -0
  72. package/src/cli/commands/help.tsx +85 -0
  73. package/src/cli/commands/index.ts +6 -0
  74. package/src/cli/commands/list.tsx +238 -0
  75. package/src/cli/commands/render.tsx +71 -0
  76. package/src/cli/commands/run.tsx +511 -0
  77. package/src/cli/commands/which.tsx +253 -0
  78. package/src/cli/index.ts +114 -0
  79. package/src/cli/quiet.ts +44 -0
  80. package/src/cli/types.ts +32 -0
  81. package/src/cli/ui/components/Badge.tsx +29 -0
  82. package/src/cli/ui/components/DataTable.tsx +51 -0
  83. package/src/cli/ui/components/Header.tsx +23 -0
  84. package/src/cli/ui/components/HelpBlock.tsx +44 -0
  85. package/src/cli/ui/components/KeyValue.tsx +33 -0
  86. package/src/cli/ui/components/OptionRow.tsx +81 -0
  87. package/src/cli/ui/components/Separator.tsx +23 -0
  88. package/src/cli/ui/components/StatusBox.tsx +108 -0
  89. package/src/cli/ui/components/VargBox.tsx +51 -0
  90. package/src/cli/ui/components/VargProgress.tsx +36 -0
  91. package/src/cli/ui/components/VargSpinner.tsx +34 -0
  92. package/src/cli/ui/components/VargText.tsx +56 -0
  93. package/src/cli/ui/components/index.ts +19 -0
  94. package/src/cli/ui/index.ts +12 -0
  95. package/src/cli/ui/render.ts +35 -0
  96. package/src/cli/ui/theme.ts +63 -0
  97. package/src/cli/utils.ts +78 -0
  98. package/src/core/executor/executor.ts +201 -0
  99. package/src/core/executor/index.ts +13 -0
  100. package/src/core/executor/job.ts +214 -0
  101. package/src/core/executor/pipeline.ts +222 -0
  102. package/src/core/index.ts +11 -0
  103. package/src/core/registry/index.ts +9 -0
  104. package/src/core/registry/loader.ts +149 -0
  105. package/src/core/registry/registry.ts +221 -0
  106. package/src/core/registry/resolver.ts +206 -0
  107. package/src/core/schema/helpers.ts +134 -0
  108. package/src/core/schema/index.ts +8 -0
  109. package/src/core/schema/shared.ts +102 -0
  110. package/src/core/schema/types.ts +279 -0
  111. package/src/core/schema/validator.ts +92 -0
  112. package/src/definitions/actions/captions.ts +261 -0
  113. package/src/definitions/actions/edit.ts +298 -0
  114. package/src/definitions/actions/image.ts +125 -0
  115. package/src/definitions/actions/index.ts +114 -0
  116. package/src/definitions/actions/music.ts +205 -0
  117. package/src/definitions/actions/sync.ts +128 -0
  118. package/{action/transcribe/index.ts → src/definitions/actions/transcribe.ts} +58 -68
  119. package/src/definitions/actions/upload.ts +111 -0
  120. package/src/definitions/actions/video.ts +163 -0
  121. package/src/definitions/actions/voice.ts +119 -0
  122. package/src/definitions/index.ts +23 -0
  123. package/src/definitions/models/elevenlabs.ts +50 -0
  124. package/src/definitions/models/flux.ts +56 -0
  125. package/src/definitions/models/index.ts +36 -0
  126. package/src/definitions/models/kling.ts +56 -0
  127. package/src/definitions/models/llama.ts +54 -0
  128. package/src/definitions/models/nano-banana-pro.ts +102 -0
  129. package/src/definitions/models/sonauto.ts +68 -0
  130. package/src/definitions/models/soul.ts +65 -0
  131. package/src/definitions/models/wan.ts +54 -0
  132. package/src/definitions/models/whisper.ts +44 -0
  133. package/src/definitions/skills/index.ts +12 -0
  134. package/src/definitions/skills/talking-character.ts +87 -0
  135. package/src/definitions/skills/text-to-tiktok.ts +97 -0
  136. package/src/index.ts +118 -0
  137. package/src/providers/apify.ts +269 -0
  138. package/src/providers/base.ts +264 -0
  139. package/src/providers/elevenlabs.ts +217 -0
  140. package/src/providers/fal.ts +392 -0
  141. package/src/providers/ffmpeg.ts +544 -0
  142. package/src/providers/fireworks.ts +193 -0
  143. package/src/providers/groq.ts +149 -0
  144. package/src/providers/higgsfield.ts +145 -0
  145. package/src/providers/index.ts +143 -0
  146. package/src/providers/replicate.ts +147 -0
  147. package/src/providers/storage.ts +206 -0
  148. package/src/react/cli.ts +52 -0
  149. package/src/react/elements.ts +146 -0
  150. package/src/react/examples/branching.tsx +66 -0
  151. package/src/react/examples/captions-demo.tsx +37 -0
  152. package/src/react/examples/character-video.tsx +84 -0
  153. package/src/react/examples/grid.tsx +53 -0
  154. package/src/react/examples/layouts-demo.tsx +57 -0
  155. package/src/react/examples/madi.tsx +60 -0
  156. package/src/react/examples/music-test.tsx +35 -0
  157. package/src/react/examples/onlyfans-1m/workflow.tsx +88 -0
  158. package/src/react/examples/orange-portrait.tsx +41 -0
  159. package/src/react/examples/split-element-demo.tsx +60 -0
  160. package/src/react/examples/split-layout-demo.tsx +60 -0
  161. package/src/react/examples/split.tsx +41 -0
  162. package/src/react/examples/video-grid.tsx +46 -0
  163. package/src/react/index.ts +43 -0
  164. package/src/react/layouts/grid.tsx +28 -0
  165. package/src/react/layouts/index.ts +2 -0
  166. package/src/react/layouts/split.tsx +20 -0
  167. package/src/react/react.test.ts +309 -0
  168. package/src/react/render.ts +21 -0
  169. package/src/react/renderers/animate.ts +59 -0
  170. package/src/react/renderers/captions.ts +297 -0
  171. package/src/react/renderers/clip.ts +248 -0
  172. package/src/react/renderers/context.ts +17 -0
  173. package/src/react/renderers/image.ts +109 -0
  174. package/src/react/renderers/index.ts +22 -0
  175. package/src/react/renderers/music.ts +60 -0
  176. package/src/react/renderers/packshot.ts +84 -0
  177. package/src/react/renderers/progress.ts +173 -0
  178. package/src/react/renderers/render.ts +243 -0
  179. package/src/react/renderers/slider.ts +69 -0
  180. package/src/react/renderers/speech.ts +53 -0
  181. package/src/react/renderers/split.ts +91 -0
  182. package/src/react/renderers/subtitle.ts +16 -0
  183. package/src/react/renderers/swipe.ts +75 -0
  184. package/src/react/renderers/title.ts +17 -0
  185. package/src/react/renderers/utils.ts +124 -0
  186. package/src/react/renderers/video.ts +127 -0
  187. package/src/react/runtime/jsx-dev-runtime.ts +43 -0
  188. package/src/react/runtime/jsx-runtime.ts +35 -0
  189. package/src/react/types.ts +232 -0
  190. package/src/studio/index.ts +26 -0
  191. package/src/studio/scanner.ts +102 -0
  192. package/src/studio/server.ts +554 -0
  193. package/src/studio/stages.ts +251 -0
  194. package/src/studio/step-renderer.ts +279 -0
  195. package/src/studio/types.ts +60 -0
  196. package/src/studio/ui/cache.html +303 -0
  197. package/src/studio/ui/index.html +1820 -0
  198. package/src/tests/all.test.ts +509 -0
  199. package/src/tests/index.ts +33 -0
  200. package/src/tests/unit.test.ts +403 -0
  201. package/tsconfig.cli.json +8 -0
  202. package/tsconfig.json +21 -3
  203. package/TEST_RESULTS.md +0 -122
  204. package/action/captions/SKILL.md +0 -170
  205. package/action/captions/index.ts +0 -169
  206. package/action/edit/SKILL.md +0 -235
  207. package/action/edit/index.ts +0 -437
  208. package/action/image/SKILL.md +0 -140
  209. package/action/image/index.ts +0 -105
  210. package/action/sync/SKILL.md +0 -136
  211. package/action/sync/index.ts +0 -145
  212. package/action/transcribe/SKILL.md +0 -179
  213. package/action/video/SKILL.md +0 -116
  214. package/action/video/index.ts +0 -125
  215. package/action/voice/SKILL.md +0 -125
  216. package/action/voice/index.ts +0 -136
  217. package/cli/commands/find.ts +0 -58
  218. package/cli/commands/help.ts +0 -70
  219. package/cli/commands/list.ts +0 -49
  220. package/cli/commands/run.ts +0 -237
  221. package/cli/commands/which.ts +0 -66
  222. package/cli/discover.ts +0 -66
  223. package/cli/index.ts +0 -33
  224. package/cli/runner.ts +0 -65
  225. package/cli/types.ts +0 -49
  226. package/cli/ui.ts +0 -185
  227. package/index.ts +0 -75
  228. package/lib/README.md +0 -144
  229. package/lib/ai-sdk/fal.ts +0 -106
  230. package/lib/ai-sdk/replicate.ts +0 -107
  231. package/lib/elevenlabs.ts +0 -382
  232. package/lib/fal.ts +0 -467
  233. package/lib/ffmpeg.ts +0 -467
  234. package/lib/fireworks.ts +0 -235
  235. package/lib/groq.ts +0 -246
  236. package/lib/higgsfield.ts +0 -176
  237. package/lib/remotion/SKILL.md +0 -823
  238. package/lib/remotion/cli.ts +0 -115
  239. package/lib/remotion/functions.ts +0 -283
  240. package/lib/remotion/index.ts +0 -19
  241. package/lib/remotion/templates.ts +0 -73
  242. package/lib/replicate.ts +0 -304
  243. package/output.txt +0 -1
  244. package/test-import.ts +0 -7
  245. package/test-services.ts +0 -97
  246. package/utilities/s3.ts +0 -147
@@ -0,0 +1,91 @@
1
+ import { editly } from "../../ai-sdk/providers/editly";
2
+ import type { Clip, Layer } from "../../ai-sdk/providers/editly/types";
3
+ import type { SplitProps, VargElement } from "../types";
4
+ import type { RenderContext } from "./context";
5
+ import { renderImage } from "./image";
6
+ import { renderVideo } from "./video";
7
+
8
+ export async function renderSplit(
9
+ element: VargElement<"split">,
10
+ ctx: RenderContext,
11
+ ): Promise<string> {
12
+ const props = element.props as SplitProps;
13
+ const direction = props.direction ?? "horizontal";
14
+
15
+ const childPaths: string[] = [];
16
+
17
+ for (const child of element.children) {
18
+ if (!child || typeof child !== "object" || !("type" in child)) continue;
19
+ const childElement = child as VargElement;
20
+
21
+ if (childElement.type === "image") {
22
+ const path = await renderImage(childElement as VargElement<"image">, ctx);
23
+ childPaths.push(path);
24
+ } else if (childElement.type === "video") {
25
+ const path = await renderVideo(childElement as VargElement<"video">, ctx);
26
+ childPaths.push(path);
27
+ }
28
+ }
29
+
30
+ if (childPaths.length === 0) {
31
+ throw new Error("Split element requires at least one image or video child");
32
+ }
33
+
34
+ if (childPaths.length === 1) {
35
+ return childPaths[0]!;
36
+ }
37
+
38
+ const numChildren = childPaths.length;
39
+ const cellWidth =
40
+ direction === "horizontal"
41
+ ? Math.floor(ctx.width / numChildren)
42
+ : ctx.width;
43
+ const cellHeight =
44
+ direction === "vertical"
45
+ ? Math.floor(ctx.height / numChildren)
46
+ : ctx.height;
47
+
48
+ const layers: Layer[] = childPaths.map((path, i) => {
49
+ const isVideo = path.endsWith(".mp4") || path.endsWith(".webm");
50
+ const left = direction === "horizontal" ? cellWidth * i : 0;
51
+ const top = direction === "vertical" ? cellHeight * i : 0;
52
+
53
+ if (isVideo) {
54
+ return {
55
+ type: "video" as const,
56
+ path,
57
+ left,
58
+ top,
59
+ width: cellWidth,
60
+ height: cellHeight,
61
+ };
62
+ }
63
+ return {
64
+ type: "image-overlay" as const,
65
+ path,
66
+ position: { x: left, y: top },
67
+ width: cellWidth,
68
+ height: cellHeight,
69
+ };
70
+ });
71
+
72
+ layers.unshift({ type: "fill-color" as const, color: "#000000" });
73
+
74
+ const clip: Clip = {
75
+ layers,
76
+ duration: 5,
77
+ };
78
+
79
+ const outPath = `/tmp/varg-split-${Date.now()}.mp4`;
80
+
81
+ await editly({
82
+ outPath,
83
+ width: ctx.width,
84
+ height: ctx.height,
85
+ fps: ctx.fps,
86
+ clips: [clip],
87
+ });
88
+
89
+ ctx.tempFiles.push(outPath);
90
+ return outPath;
91
+ }
@@ -0,0 +1,16 @@
1
+ import type { SubtitleLayer } from "../../ai-sdk/providers/editly/types";
2
+ import type { SubtitleProps, VargElement } from "../types";
3
+ import { getTextContent } from "./utils";
4
+
5
+ export function renderSubtitle(
6
+ element: VargElement<"subtitle">,
7
+ ): SubtitleLayer {
8
+ const props = element.props as SubtitleProps;
9
+ const text = getTextContent(element.children);
10
+
11
+ return {
12
+ type: "subtitle",
13
+ text,
14
+ backgroundColor: props.backgroundColor,
15
+ };
16
+ }
@@ -0,0 +1,75 @@
1
+ import { editly } from "../../ai-sdk/providers/editly";
2
+ import type { Clip } from "../../ai-sdk/providers/editly/types";
3
+ import type { SwipeProps, VargElement } from "../types";
4
+ import type { RenderContext } from "./context";
5
+ import { renderImage } from "./image";
6
+ import { renderVideo } from "./video";
7
+
8
+ const SWIPE_TRANSITION_MAP = {
9
+ left: "slideleft",
10
+ right: "slideright",
11
+ up: "slideup",
12
+ down: "slidedown",
13
+ } as const;
14
+
15
+ export async function renderSwipe(
16
+ element: VargElement<"swipe">,
17
+ ctx: RenderContext,
18
+ ): Promise<string> {
19
+ const props = element.props as SwipeProps;
20
+ const direction = props.direction ?? "left";
21
+ const interval = props.interval ?? 3;
22
+
23
+ const childPaths: string[] = [];
24
+
25
+ for (const child of element.children) {
26
+ if (!child || typeof child !== "object" || !("type" in child)) continue;
27
+ const childElement = child as VargElement;
28
+
29
+ if (childElement.type === "image") {
30
+ const path = await renderImage(childElement as VargElement<"image">, ctx);
31
+ childPaths.push(path);
32
+ } else if (childElement.type === "video") {
33
+ const path = await renderVideo(childElement as VargElement<"video">, ctx);
34
+ childPaths.push(path);
35
+ }
36
+ }
37
+
38
+ if (childPaths.length === 0) {
39
+ throw new Error("Swipe element requires at least one image or video child");
40
+ }
41
+
42
+ if (childPaths.length === 1) {
43
+ return childPaths[0]!;
44
+ }
45
+
46
+ const transitionName = SWIPE_TRANSITION_MAP[direction];
47
+
48
+ const clips: Clip[] = childPaths.map((path, i) => {
49
+ const isVideo = path.endsWith(".mp4") || path.endsWith(".webm");
50
+ const isLast = i === childPaths.length - 1;
51
+
52
+ return {
53
+ layers: [
54
+ isVideo
55
+ ? { type: "video" as const, path, resizeMode: "cover" as const }
56
+ : { type: "image" as const, path, resizeMode: "cover" as const },
57
+ ],
58
+ duration: interval,
59
+ transition: isLast ? null : { name: transitionName, duration: 0.5 },
60
+ };
61
+ });
62
+
63
+ const outPath = `/tmp/varg-swipe-${Date.now()}.mp4`;
64
+
65
+ await editly({
66
+ outPath,
67
+ width: ctx.width,
68
+ height: ctx.height,
69
+ fps: ctx.fps,
70
+ clips,
71
+ });
72
+
73
+ ctx.tempFiles.push(outPath);
74
+ return outPath;
75
+ }
@@ -0,0 +1,17 @@
1
+ import type { TitleLayer } from "../../ai-sdk/providers/editly/types";
2
+ import type { TitleProps, VargElement } from "../types";
3
+ import { getTextContent } from "./utils";
4
+
5
+ export function renderTitle(element: VargElement<"title">): TitleLayer {
6
+ const props = element.props as TitleProps;
7
+ const text = getTextContent(element.children);
8
+
9
+ return {
10
+ type: "title",
11
+ text,
12
+ textColor: props.color,
13
+ position: props.position,
14
+ start: props.start,
15
+ stop: props.end,
16
+ };
17
+ }
@@ -0,0 +1,124 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import type { VargElement, VargNode } from "../types";
4
+
5
+ export function resolvePath(path: string): string {
6
+ if (path.startsWith("http://") || path.startsWith("https://")) {
7
+ return path;
8
+ }
9
+ return resolve(process.cwd(), path);
10
+ }
11
+
12
+ export function toFileUrl(path: string): string {
13
+ if (path.startsWith("http://") || path.startsWith("https://")) {
14
+ return path;
15
+ }
16
+ return `file://${resolvePath(path)}`;
17
+ }
18
+
19
+ type CacheKeyPart = string | number | boolean | null | undefined;
20
+
21
+ function isVargElement(v: unknown): v is VargElement {
22
+ return (
23
+ typeof v === "object" &&
24
+ v !== null &&
25
+ "type" in v &&
26
+ "props" in v &&
27
+ "children" in v
28
+ );
29
+ }
30
+
31
+ function isLocalFilePath(v: string): boolean {
32
+ if (v.startsWith("http://") || v.startsWith("https://")) return false;
33
+ if (v.startsWith("data:")) return false;
34
+ const resolved = resolvePath(v);
35
+ return existsSync(resolved);
36
+ }
37
+
38
+ function getFileFingerprint(path: string): string {
39
+ const resolved = resolvePath(path);
40
+ const stat = statSync(resolved);
41
+ return `${path}:${stat.mtimeMs}:${stat.size}`;
42
+ }
43
+
44
+ function serializeValue(v: unknown): string {
45
+ if (typeof v === "string") {
46
+ if (isLocalFilePath(v)) {
47
+ return getFileFingerprint(v);
48
+ }
49
+ return v;
50
+ }
51
+ if (v instanceof Uint8Array) {
52
+ return Buffer.from(v).toString("base64");
53
+ }
54
+ if (Array.isArray(v)) {
55
+ return `[${v.map(serializeValue).join(",")}]`;
56
+ }
57
+ if (v && typeof v === "object") {
58
+ const entries = Object.entries(v)
59
+ .map(([key, val]) => `${key}:${serializeValue(val)}`)
60
+ .join(",");
61
+ return `{${entries}}`;
62
+ }
63
+ return String(v);
64
+ }
65
+
66
+ export function computeCacheKey(element: VargElement): CacheKeyPart[] {
67
+ const key: CacheKeyPart[] = [element.type];
68
+
69
+ for (const [k, v] of Object.entries(element.props)) {
70
+ if (k === "children") continue;
71
+ if (k === "model" && v && typeof v === "object" && "modelId" in v) {
72
+ const model = v as {
73
+ provider?: string;
74
+ modelId: string;
75
+ settings?: Record<string, unknown>;
76
+ };
77
+ key.push("model", model.provider ?? "", model.modelId);
78
+ // Include model settings in cache key (e.g., higgsfield styleId, quality)
79
+ if (model.settings) {
80
+ key.push("modelSettings", serializeValue(model.settings));
81
+ }
82
+ continue;
83
+ }
84
+ if (typeof v === "string") {
85
+ if (isLocalFilePath(v)) {
86
+ key.push(k, getFileFingerprint(v));
87
+ } else {
88
+ key.push(k, v);
89
+ }
90
+ } else if (typeof v === "number" || typeof v === "boolean") {
91
+ key.push(k, v);
92
+ } else if (v === null || v === undefined) {
93
+ key.push(k, v);
94
+ } else if (v instanceof Uint8Array) {
95
+ key.push(k, Buffer.from(v).toString("base64"));
96
+ } else if (isVargElement(v)) {
97
+ key.push(k, ...computeCacheKey(v));
98
+ } else if (Array.isArray(v) || typeof v === "object") {
99
+ key.push(k, serializeValue(v));
100
+ }
101
+ }
102
+
103
+ for (const child of element.children) {
104
+ if (typeof child === "string") {
105
+ key.push("text", child);
106
+ } else if (typeof child === "number") {
107
+ key.push("num", child);
108
+ } else if (isVargElement(child)) {
109
+ key.push("child", ...computeCacheKey(child));
110
+ }
111
+ }
112
+
113
+ return key;
114
+ }
115
+
116
+ export function getTextContent(node: VargNode): string {
117
+ if (typeof node === "string") return node;
118
+ if (typeof node === "number") return String(node);
119
+ if (Array.isArray(node)) return node.map(getTextContent).join("");
120
+ if (node && typeof node === "object" && "children" in node) {
121
+ return node.children.map(getTextContent).join("");
122
+ }
123
+ return "";
124
+ }
@@ -0,0 +1,127 @@
1
+ import { File } from "../../ai-sdk/file";
2
+ import type { generateVideo } from "../../ai-sdk/generate-video";
3
+ import type {
4
+ ImageInput,
5
+ VargElement,
6
+ VideoPrompt,
7
+ VideoProps,
8
+ } from "../types";
9
+ import type { RenderContext } from "./context";
10
+ import { renderImage } from "./image";
11
+ import { addTask, completeTask, startTask } from "./progress";
12
+ import { computeCacheKey, toFileUrl } from "./utils";
13
+
14
+ async function resolveImageInput(
15
+ input: ImageInput,
16
+ ctx: RenderContext,
17
+ ): Promise<Uint8Array> {
18
+ if (input instanceof Uint8Array) {
19
+ return input;
20
+ }
21
+ if (typeof input === "string") {
22
+ const response = await fetch(toFileUrl(input));
23
+ return new Uint8Array(await response.arrayBuffer());
24
+ }
25
+ const path = await renderImage(input, ctx);
26
+ const response = await fetch(toFileUrl(path));
27
+ return new Uint8Array(await response.arrayBuffer());
28
+ }
29
+
30
+ async function resolveMediaInput(
31
+ input: Uint8Array | string | undefined,
32
+ ): Promise<Uint8Array | undefined> {
33
+ if (!input) return undefined;
34
+ if (input instanceof Uint8Array) return input;
35
+ const response = await fetch(toFileUrl(input));
36
+ return new Uint8Array(await response.arrayBuffer());
37
+ }
38
+
39
+ async function resolvePrompt(
40
+ prompt: VideoPrompt,
41
+ ctx: RenderContext,
42
+ ): Promise<
43
+ | string
44
+ | {
45
+ text?: string;
46
+ images?: Uint8Array[];
47
+ audio?: Uint8Array;
48
+ video?: Uint8Array;
49
+ }
50
+ > {
51
+ if (typeof prompt === "string") {
52
+ return prompt;
53
+ }
54
+ const [resolvedImages, resolvedAudio, resolvedVideo] = await Promise.all([
55
+ prompt.images
56
+ ? Promise.all(prompt.images.map((img) => resolveImageInput(img, ctx)))
57
+ : undefined,
58
+ resolveMediaInput(prompt.audio),
59
+ resolveMediaInput(prompt.video),
60
+ ]);
61
+ return {
62
+ text: prompt.text,
63
+ images: resolvedImages,
64
+ audio: resolvedAudio,
65
+ video: resolvedVideo,
66
+ };
67
+ }
68
+
69
+ export async function renderVideo(
70
+ element: VargElement<"video">,
71
+ ctx: RenderContext,
72
+ ): Promise<string> {
73
+ const props = element.props as VideoProps;
74
+
75
+ if (props.src && !props.prompt) {
76
+ return props.src;
77
+ }
78
+
79
+ const prompt = props.prompt;
80
+ if (!prompt) {
81
+ throw new Error("Video element requires either 'prompt' or 'src'");
82
+ }
83
+
84
+ const model = props.model;
85
+ if (!model) {
86
+ throw new Error("Video element requires 'model' prop when using prompt");
87
+ }
88
+
89
+ // Compute cache key for deduplication
90
+ const cacheKey = computeCacheKey(element);
91
+ const cacheKeyStr = JSON.stringify(cacheKey);
92
+
93
+ // Check if this element is already being rendered (deduplication)
94
+ const pendingRender = ctx.pending.get(cacheKeyStr);
95
+ if (pendingRender) {
96
+ return pendingRender;
97
+ }
98
+
99
+ // Create the render promise and store it for deduplication
100
+ const renderPromise = (async () => {
101
+ const resolvedPrompt = await resolvePrompt(prompt, ctx);
102
+
103
+ const modelId = typeof model === "string" ? model : model.modelId;
104
+ const taskId = ctx.progress
105
+ ? addTask(ctx.progress, "video", modelId)
106
+ : null;
107
+ if (taskId && ctx.progress) startTask(ctx.progress, taskId);
108
+
109
+ const { video } = await ctx.generateVideo({
110
+ model,
111
+ prompt: resolvedPrompt,
112
+ duration: 5,
113
+ cacheKey,
114
+ } as Parameters<typeof generateVideo>[0]);
115
+
116
+ if (taskId && ctx.progress) completeTask(ctx.progress, taskId);
117
+
118
+ const tempPath = await File.toTemp(video);
119
+ ctx.tempFiles.push(tempPath);
120
+
121
+ return tempPath;
122
+ })();
123
+
124
+ ctx.pending.set(cacheKeyStr, renderPromise);
125
+
126
+ return renderPromise;
127
+ }
@@ -0,0 +1,43 @@
1
+ import type { VargElement, VargNode } from "../types";
2
+
3
+ type ElementFactory = (props: Record<string, unknown>) => VargElement;
4
+
5
+ export function jsx(
6
+ type: ElementFactory,
7
+ props: Record<string, unknown> | null,
8
+ key?: string,
9
+ ): VargElement {
10
+ const finalProps = { ...props };
11
+ if (key !== undefined) {
12
+ finalProps.key = key;
13
+ }
14
+ return type(finalProps);
15
+ }
16
+
17
+ export function jsxs(
18
+ type: ElementFactory,
19
+ props: Record<string, unknown> | null,
20
+ key?: string,
21
+ ): VargElement {
22
+ return jsx(type, props, key);
23
+ }
24
+
25
+ export function jsxDEV(
26
+ type: ElementFactory,
27
+ props: Record<string, unknown> | null,
28
+ key?: string,
29
+ ): VargElement {
30
+ return jsx(type, props, key);
31
+ }
32
+
33
+ export const Fragment = ({ children }: { children?: VargNode }) => children;
34
+
35
+ export namespace JSX {
36
+ export type Element = VargElement;
37
+ // biome-ignore lint/complexity/noBannedTypes: required for JSX namespace
38
+ export type IntrinsicElements = {};
39
+ export interface ElementChildrenAttribute {
40
+ // biome-ignore lint/complexity/noBannedTypes: required for JSX namespace
41
+ children: {};
42
+ }
43
+ }
@@ -0,0 +1,35 @@
1
+ import type { VargElement, VargNode } from "../types";
2
+
3
+ type ElementFactory = (props: Record<string, unknown>) => VargElement;
4
+
5
+ export function jsx(
6
+ type: ElementFactory,
7
+ props: Record<string, unknown> | null,
8
+ key?: string,
9
+ ): VargElement {
10
+ const finalProps = { ...props };
11
+ if (key !== undefined) {
12
+ finalProps.key = key;
13
+ }
14
+ return type(finalProps);
15
+ }
16
+
17
+ export function jsxs(
18
+ type: ElementFactory,
19
+ props: Record<string, unknown> | null,
20
+ key?: string,
21
+ ): VargElement {
22
+ return jsx(type, props, key);
23
+ }
24
+
25
+ export const Fragment = ({ children }: { children?: VargNode }) => children;
26
+
27
+ export namespace JSX {
28
+ export type Element = VargElement;
29
+ // biome-ignore lint/complexity/noBannedTypes: required for JSX namespace
30
+ export type IntrinsicElements = {};
31
+ export interface ElementChildrenAttribute {
32
+ // biome-ignore lint/complexity/noBannedTypes: required for JSX namespace
33
+ children: {};
34
+ }
35
+ }