varg.ai-sdk 0.1.0 → 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.
- package/.claude/settings.local.json +1 -1
- package/.env.example +3 -0
- package/.github/workflows/ci.yml +23 -0
- package/.husky/README.md +102 -0
- package/.husky/commit-msg +6 -0
- package/.husky/pre-commit +9 -0
- package/.husky/pre-push +6 -0
- package/.size-limit.json +8 -0
- package/.test-hooks.ts +5 -0
- package/CLAUDE.md +10 -3
- package/CONTRIBUTING.md +150 -0
- package/LICENSE.md +53 -0
- package/README.md +56 -209
- package/SKILLS.md +26 -10
- package/biome.json +7 -1
- package/bun.lock +1286 -0
- package/commitlint.config.js +22 -0
- package/docs/index.html +1130 -0
- package/docs/prompting.md +326 -0
- package/docs/react.md +834 -0
- package/docs/sdk.md +812 -0
- package/ffmpeg/CLAUDE.md +68 -0
- package/package.json +48 -8
- package/pipeline/cookbooks/scripts/animate-frames-parallel.ts +84 -0
- package/pipeline/cookbooks/scripts/combine-scenes.sh +53 -0
- package/pipeline/cookbooks/scripts/generate-frames-parallel.ts +99 -0
- package/pipeline/cookbooks/scripts/still-to-video.sh +37 -0
- package/pipeline/cookbooks/text-to-tiktok.md +669 -0
- package/pipeline/cookbooks/trendwatching.md +156 -0
- package/plan.md +281 -0
- package/scripts/.gitkeep +0 -0
- package/src/ai-sdk/cache.ts +142 -0
- package/src/ai-sdk/examples/cached-generation.ts +53 -0
- package/src/ai-sdk/examples/duet-scene-4.ts +53 -0
- package/src/ai-sdk/examples/duet-scene-5-audio.ts +32 -0
- package/src/ai-sdk/examples/duet-video.ts +56 -0
- package/src/ai-sdk/examples/editly-composition.ts +63 -0
- package/src/ai-sdk/examples/editly-test.ts +57 -0
- package/src/ai-sdk/examples/editly-video-test.ts +52 -0
- package/src/ai-sdk/examples/fal-lipsync.ts +43 -0
- package/src/ai-sdk/examples/higgsfield-image.ts +61 -0
- package/src/ai-sdk/examples/music-generation.ts +19 -0
- package/src/ai-sdk/examples/openai-sora.ts +34 -0
- package/src/ai-sdk/examples/replicate-bg-removal.ts +52 -0
- package/src/ai-sdk/examples/simpsons-scene.ts +61 -0
- package/src/ai-sdk/examples/talking-lion.ts +55 -0
- package/src/ai-sdk/examples/video-generation.ts +39 -0
- package/src/ai-sdk/examples/workflow-animated-girl.ts +104 -0
- package/src/ai-sdk/examples/workflow-before-after.ts +114 -0
- package/src/ai-sdk/examples/workflow-character-grid.ts +112 -0
- package/src/ai-sdk/examples/workflow-slideshow.ts +161 -0
- package/src/ai-sdk/file-cache.ts +112 -0
- package/src/ai-sdk/file.ts +238 -0
- package/src/ai-sdk/generate-element.ts +92 -0
- package/src/ai-sdk/generate-music.ts +46 -0
- package/src/ai-sdk/generate-video.ts +165 -0
- package/src/ai-sdk/index.ts +72 -0
- package/src/ai-sdk/music-model.ts +110 -0
- package/src/ai-sdk/providers/editly/editly.test.ts +1108 -0
- package/src/ai-sdk/providers/editly/ffmpeg.ts +60 -0
- package/src/ai-sdk/providers/editly/index.ts +817 -0
- package/src/ai-sdk/providers/editly/layers.ts +776 -0
- package/src/ai-sdk/providers/editly/plan.md +144 -0
- package/src/ai-sdk/providers/editly/types.ts +328 -0
- package/src/ai-sdk/providers/elevenlabs-provider.ts +255 -0
- package/src/ai-sdk/providers/fal-provider.ts +512 -0
- package/src/ai-sdk/providers/higgsfield.ts +379 -0
- package/src/ai-sdk/providers/openai.ts +251 -0
- package/src/ai-sdk/providers/replicate.ts +16 -0
- package/src/ai-sdk/video-model.ts +185 -0
- package/src/cli/commands/find.tsx +137 -0
- package/src/cli/commands/help.tsx +85 -0
- package/src/cli/commands/index.ts +6 -0
- package/src/cli/commands/list.tsx +238 -0
- package/src/cli/commands/render.tsx +71 -0
- package/src/cli/commands/run.tsx +511 -0
- package/src/cli/commands/which.tsx +253 -0
- package/src/cli/index.ts +114 -0
- package/src/cli/quiet.ts +44 -0
- package/src/cli/types.ts +32 -0
- package/src/cli/ui/components/Badge.tsx +29 -0
- package/src/cli/ui/components/DataTable.tsx +51 -0
- package/src/cli/ui/components/Header.tsx +23 -0
- package/src/cli/ui/components/HelpBlock.tsx +44 -0
- package/src/cli/ui/components/KeyValue.tsx +33 -0
- package/src/cli/ui/components/OptionRow.tsx +81 -0
- package/src/cli/ui/components/Separator.tsx +23 -0
- package/src/cli/ui/components/StatusBox.tsx +108 -0
- package/src/cli/ui/components/VargBox.tsx +51 -0
- package/src/cli/ui/components/VargProgress.tsx +36 -0
- package/src/cli/ui/components/VargSpinner.tsx +34 -0
- package/src/cli/ui/components/VargText.tsx +56 -0
- package/src/cli/ui/components/index.ts +19 -0
- package/src/cli/ui/index.ts +12 -0
- package/src/cli/ui/render.ts +35 -0
- package/src/cli/ui/theme.ts +63 -0
- package/src/cli/utils.ts +78 -0
- package/src/core/executor/executor.ts +201 -0
- package/src/core/executor/index.ts +13 -0
- package/src/core/executor/job.ts +214 -0
- package/src/core/executor/pipeline.ts +222 -0
- package/src/core/index.ts +11 -0
- package/src/core/registry/index.ts +9 -0
- package/src/core/registry/loader.ts +149 -0
- package/src/core/registry/registry.ts +221 -0
- package/src/core/registry/resolver.ts +206 -0
- package/src/core/schema/helpers.ts +134 -0
- package/src/core/schema/index.ts +8 -0
- package/src/core/schema/shared.ts +102 -0
- package/src/core/schema/types.ts +279 -0
- package/src/core/schema/validator.ts +92 -0
- package/src/definitions/actions/captions.ts +261 -0
- package/src/definitions/actions/edit.ts +298 -0
- package/src/definitions/actions/image.ts +125 -0
- package/src/definitions/actions/index.ts +114 -0
- package/src/definitions/actions/music.ts +205 -0
- package/src/definitions/actions/sync.ts +128 -0
- package/{action/transcribe/index.ts → src/definitions/actions/transcribe.ts} +63 -90
- package/src/definitions/actions/upload.ts +111 -0
- package/src/definitions/actions/video.ts +163 -0
- package/src/definitions/actions/voice.ts +119 -0
- package/src/definitions/index.ts +23 -0
- package/src/definitions/models/elevenlabs.ts +50 -0
- package/src/definitions/models/flux.ts +56 -0
- package/src/definitions/models/index.ts +36 -0
- package/src/definitions/models/kling.ts +56 -0
- package/src/definitions/models/llama.ts +54 -0
- package/src/definitions/models/nano-banana-pro.ts +102 -0
- package/src/definitions/models/sonauto.ts +68 -0
- package/src/definitions/models/soul.ts +65 -0
- package/src/definitions/models/wan.ts +54 -0
- package/src/definitions/models/whisper.ts +44 -0
- package/src/definitions/skills/index.ts +12 -0
- package/src/definitions/skills/talking-character.ts +87 -0
- package/src/definitions/skills/text-to-tiktok.ts +97 -0
- package/src/index.ts +118 -0
- package/src/providers/apify.ts +269 -0
- package/src/providers/base.ts +264 -0
- package/src/providers/elevenlabs.ts +217 -0
- package/src/providers/fal.ts +392 -0
- package/src/providers/ffmpeg.ts +544 -0
- package/src/providers/fireworks.ts +193 -0
- package/src/providers/groq.ts +149 -0
- package/src/providers/higgsfield.ts +145 -0
- package/src/providers/index.ts +143 -0
- package/src/providers/replicate.ts +147 -0
- package/src/providers/storage.ts +206 -0
- package/src/react/cli.ts +52 -0
- package/src/react/elements.ts +146 -0
- package/src/react/examples/branching.tsx +66 -0
- package/src/react/examples/captions-demo.tsx +37 -0
- package/src/react/examples/character-video.tsx +84 -0
- package/src/react/examples/grid.tsx +53 -0
- package/src/react/examples/layouts-demo.tsx +57 -0
- package/src/react/examples/madi.tsx +60 -0
- package/src/react/examples/music-test.tsx +35 -0
- package/src/react/examples/onlyfans-1m/workflow.tsx +88 -0
- package/src/react/examples/orange-portrait.tsx +41 -0
- package/src/react/examples/split-element-demo.tsx +60 -0
- package/src/react/examples/split-layout-demo.tsx +60 -0
- package/src/react/examples/split.tsx +41 -0
- package/src/react/examples/video-grid.tsx +46 -0
- package/src/react/index.ts +43 -0
- package/src/react/layouts/grid.tsx +28 -0
- package/src/react/layouts/index.ts +2 -0
- package/src/react/layouts/split.tsx +20 -0
- package/src/react/react.test.ts +309 -0
- package/src/react/render.ts +21 -0
- package/src/react/renderers/animate.ts +59 -0
- package/src/react/renderers/captions.ts +297 -0
- package/src/react/renderers/clip.ts +248 -0
- package/src/react/renderers/context.ts +17 -0
- package/src/react/renderers/image.ts +109 -0
- package/src/react/renderers/index.ts +22 -0
- package/src/react/renderers/music.ts +60 -0
- package/src/react/renderers/packshot.ts +84 -0
- package/src/react/renderers/progress.ts +173 -0
- package/src/react/renderers/render.ts +243 -0
- package/src/react/renderers/slider.ts +69 -0
- package/src/react/renderers/speech.ts +53 -0
- package/src/react/renderers/split.ts +91 -0
- package/src/react/renderers/subtitle.ts +16 -0
- package/src/react/renderers/swipe.ts +75 -0
- package/src/react/renderers/title.ts +17 -0
- package/src/react/renderers/utils.ts +124 -0
- package/src/react/renderers/video.ts +127 -0
- package/src/react/runtime/jsx-dev-runtime.ts +43 -0
- package/src/react/runtime/jsx-runtime.ts +35 -0
- package/src/react/types.ts +232 -0
- package/src/studio/index.ts +26 -0
- package/src/studio/scanner.ts +102 -0
- package/src/studio/server.ts +554 -0
- package/src/studio/stages.ts +251 -0
- package/src/studio/step-renderer.ts +279 -0
- package/src/studio/types.ts +60 -0
- package/src/studio/ui/cache.html +303 -0
- package/src/studio/ui/index.html +1820 -0
- package/src/tests/all.test.ts +509 -0
- package/src/tests/index.ts +33 -0
- package/src/tests/unit.test.ts +403 -0
- package/tsconfig.cli.json +8 -0
- package/tsconfig.json +21 -3
- package/TEST_RESULTS.md +0 -122
- package/action/captions/SKILL.md +0 -170
- package/action/captions/index.ts +0 -227
- package/action/edit/SKILL.md +0 -235
- package/action/edit/index.ts +0 -493
- package/action/image/SKILL.md +0 -140
- package/action/image/index.ts +0 -112
- package/action/sync/SKILL.md +0 -136
- package/action/sync/index.ts +0 -187
- package/action/transcribe/SKILL.md +0 -179
- package/action/video/SKILL.md +0 -116
- package/action/video/index.ts +0 -135
- package/action/voice/SKILL.md +0 -125
- package/action/voice/index.ts +0 -201
- package/index.ts +0 -38
- package/lib/README.md +0 -144
- package/lib/ai-sdk/fal.ts +0 -106
- package/lib/ai-sdk/replicate.ts +0 -107
- package/lib/elevenlabs.ts +0 -382
- package/lib/fal.ts +0 -478
- package/lib/ffmpeg.ts +0 -467
- package/lib/fireworks.ts +0 -235
- package/lib/groq.ts +0 -246
- package/lib/higgsfield.ts +0 -176
- package/lib/remotion/SKILL.md +0 -823
- package/lib/remotion/cli.ts +0 -115
- package/lib/remotion/functions.ts +0 -283
- package/lib/remotion/index.ts +0 -19
- package/lib/remotion/templates.ts +0 -73
- package/lib/replicate.ts +0 -304
- package/output.txt +0 -1
- package/test-import.ts +0 -7
- package/test-services.ts +0 -97
- package/utilities/s3.ts +0 -147
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type { VargElement, VargNode } from "../react/types";
|
|
2
|
+
|
|
3
|
+
export type StageType = "image" | "video" | "animate" | "speech" | "music";
|
|
4
|
+
|
|
5
|
+
export interface RenderStage {
|
|
6
|
+
id: string;
|
|
7
|
+
type: StageType;
|
|
8
|
+
label: string;
|
|
9
|
+
element: VargElement;
|
|
10
|
+
path: string[]; // path in the tree for identification
|
|
11
|
+
dependsOn: string[]; // stage ids this depends on
|
|
12
|
+
status: "pending" | "running" | "complete" | "error";
|
|
13
|
+
result?: StageResult;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface StageResult {
|
|
17
|
+
type: "image" | "video" | "audio";
|
|
18
|
+
path: string;
|
|
19
|
+
previewUrl?: string;
|
|
20
|
+
mimeType: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ExtractedStages {
|
|
24
|
+
stages: RenderStage[];
|
|
25
|
+
order: string[]; // topologically sorted stage ids
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extracts all renderable stages from a JSX tree in dependency order.
|
|
30
|
+
* Images come before videos that use them, etc.
|
|
31
|
+
*/
|
|
32
|
+
export function extractStages(element: VargElement): ExtractedStages {
|
|
33
|
+
const stages: RenderStage[] = [];
|
|
34
|
+
let stageCounter = 0;
|
|
35
|
+
|
|
36
|
+
function generateId(): string {
|
|
37
|
+
return `stage-${stageCounter++}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getLabel(type: StageType, element: VargElement): string {
|
|
41
|
+
const props = element.props as Record<string, unknown>;
|
|
42
|
+
|
|
43
|
+
if (type === "image") {
|
|
44
|
+
const prompt = props.prompt;
|
|
45
|
+
if (typeof prompt === "string") {
|
|
46
|
+
return `image: ${prompt.slice(0, 30)}${prompt.length > 30 ? "..." : ""}`;
|
|
47
|
+
}
|
|
48
|
+
if (prompt && typeof prompt === "object" && "text" in prompt) {
|
|
49
|
+
const text = (prompt as { text?: string }).text ?? "";
|
|
50
|
+
return `image: ${text.slice(0, 30)}${text.length > 30 ? "..." : ""}`;
|
|
51
|
+
}
|
|
52
|
+
if (props.src) {
|
|
53
|
+
return `image: ${String(props.src).split("/").pop()}`;
|
|
54
|
+
}
|
|
55
|
+
return "image";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (type === "video") {
|
|
59
|
+
const prompt = props.prompt;
|
|
60
|
+
if (typeof prompt === "string") {
|
|
61
|
+
return `video: ${prompt.slice(0, 30)}${prompt.length > 30 ? "..." : ""}`;
|
|
62
|
+
}
|
|
63
|
+
if (prompt && typeof prompt === "object" && "text" in prompt) {
|
|
64
|
+
const text = (prompt as { text?: string }).text ?? "";
|
|
65
|
+
return `video: ${text.slice(0, 30)}${text.length > 30 ? "..." : ""}`;
|
|
66
|
+
}
|
|
67
|
+
if (props.src) {
|
|
68
|
+
return `video: ${String(props.src).split("/").pop()}`;
|
|
69
|
+
}
|
|
70
|
+
return "video";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (type === "animate") {
|
|
74
|
+
const motion = props.motion;
|
|
75
|
+
return motion ? `animate: ${motion}` : "animate";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (type === "speech") {
|
|
79
|
+
const text = getTextContent(element.children);
|
|
80
|
+
return `speech: ${text.slice(0, 30)}${text.length > 30 ? "..." : ""}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (type === "music") {
|
|
84
|
+
const prompt = props.prompt;
|
|
85
|
+
if (typeof prompt === "string") {
|
|
86
|
+
return `music: ${prompt.slice(0, 30)}${prompt.length > 30 ? "..." : ""}`;
|
|
87
|
+
}
|
|
88
|
+
if (props.src) {
|
|
89
|
+
return `music: ${String(props.src).split("/").pop()}`;
|
|
90
|
+
}
|
|
91
|
+
return "music";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return type;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getTextContent(children: VargNode[]): string {
|
|
98
|
+
const texts: string[] = [];
|
|
99
|
+
for (const child of children) {
|
|
100
|
+
if (typeof child === "string") {
|
|
101
|
+
texts.push(child);
|
|
102
|
+
} else if (typeof child === "number") {
|
|
103
|
+
texts.push(String(child));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return texts.join(" ");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function walkTree(
|
|
110
|
+
node: VargNode,
|
|
111
|
+
path: string[],
|
|
112
|
+
parentDeps: string[],
|
|
113
|
+
): string[] {
|
|
114
|
+
if (!node || typeof node !== "object" || !("type" in node)) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const element = node as VargElement;
|
|
119
|
+
const currentPath = [...path, element.type];
|
|
120
|
+
const collectedDeps: string[] = [...parentDeps];
|
|
121
|
+
|
|
122
|
+
// Check if this is a renderable stage
|
|
123
|
+
const stageTypes: StageType[] = [
|
|
124
|
+
"image",
|
|
125
|
+
"video",
|
|
126
|
+
"animate",
|
|
127
|
+
"speech",
|
|
128
|
+
"music",
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
if (stageTypes.includes(element.type as StageType)) {
|
|
132
|
+
const stageType = element.type as StageType;
|
|
133
|
+
const props = element.props as Record<string, unknown>;
|
|
134
|
+
|
|
135
|
+
// Skip if it's just a src reference (no generation needed)
|
|
136
|
+
if (props.src && !props.prompt) {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// For video/animate with image inputs, we need to find dependent images first
|
|
141
|
+
const imageDeps: string[] = [];
|
|
142
|
+
|
|
143
|
+
if (stageType === "video" || stageType === "animate") {
|
|
144
|
+
// Check prompt.images for nested Image elements
|
|
145
|
+
const prompt = props.prompt as { images?: VargNode[] } | undefined;
|
|
146
|
+
if (prompt?.images) {
|
|
147
|
+
for (const imgInput of prompt.images) {
|
|
148
|
+
if (
|
|
149
|
+
imgInput &&
|
|
150
|
+
typeof imgInput === "object" &&
|
|
151
|
+
"type" in imgInput
|
|
152
|
+
) {
|
|
153
|
+
const imgElement = imgInput as VargElement;
|
|
154
|
+
if (imgElement.type === "image") {
|
|
155
|
+
const deps = walkTree(imgElement, currentPath, collectedDeps);
|
|
156
|
+
imageDeps.push(...deps);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Check for image prop in animate
|
|
163
|
+
if (stageType === "animate" && props.image) {
|
|
164
|
+
const imgElement = props.image as VargElement;
|
|
165
|
+
if (imgElement.type === "image") {
|
|
166
|
+
const deps = walkTree(imgElement, currentPath, collectedDeps);
|
|
167
|
+
imageDeps.push(...deps);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const id = generateId();
|
|
173
|
+
const stage: RenderStage = {
|
|
174
|
+
id,
|
|
175
|
+
type: stageType,
|
|
176
|
+
label: getLabel(stageType, element),
|
|
177
|
+
element,
|
|
178
|
+
path: currentPath,
|
|
179
|
+
dependsOn: [...new Set([...collectedDeps, ...imageDeps])],
|
|
180
|
+
status: "pending",
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
stages.push(stage);
|
|
184
|
+
return [id];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// For container elements (render, clip, overlay), recurse into children
|
|
188
|
+
const childDeps: string[] = [];
|
|
189
|
+
for (const child of element.children) {
|
|
190
|
+
const deps = walkTree(child, currentPath, collectedDeps);
|
|
191
|
+
childDeps.push(...deps);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return childDeps;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Start walking from root
|
|
198
|
+
if (element.type === "render") {
|
|
199
|
+
walkTree(element, [], []);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Topological sort based on dependencies
|
|
203
|
+
const order = topologicalSort(stages);
|
|
204
|
+
|
|
205
|
+
return { stages, order };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function topologicalSort(stages: RenderStage[]): string[] {
|
|
209
|
+
const result: string[] = [];
|
|
210
|
+
const visited = new Set<string>();
|
|
211
|
+
const visiting = new Set<string>();
|
|
212
|
+
|
|
213
|
+
const stageMap = new Map(stages.map((s) => [s.id, s]));
|
|
214
|
+
|
|
215
|
+
function visit(id: string) {
|
|
216
|
+
if (visited.has(id)) return;
|
|
217
|
+
if (visiting.has(id)) {
|
|
218
|
+
throw new Error(`Circular dependency detected at stage ${id}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
visiting.add(id);
|
|
222
|
+
const stage = stageMap.get(id);
|
|
223
|
+
if (stage) {
|
|
224
|
+
for (const depId of stage.dependsOn) {
|
|
225
|
+
visit(depId);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
visiting.delete(id);
|
|
229
|
+
visited.add(id);
|
|
230
|
+
result.push(id);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const stage of stages) {
|
|
234
|
+
visit(stage.id);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Serializes stages for API response (strips non-serializable element)
|
|
242
|
+
*/
|
|
243
|
+
export function serializeStages(extracted: ExtractedStages): {
|
|
244
|
+
stages: Omit<RenderStage, "element">[];
|
|
245
|
+
order: string[];
|
|
246
|
+
} {
|
|
247
|
+
return {
|
|
248
|
+
stages: extracted.stages.map(({ element, ...rest }) => rest),
|
|
249
|
+
order: extracted.order,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { generateImage } from "ai";
|
|
2
|
+
import { withCache } from "../ai-sdk/cache";
|
|
3
|
+
import { fileCache } from "../ai-sdk/file-cache";
|
|
4
|
+
import { generateVideo } from "../ai-sdk/generate-video";
|
|
5
|
+
import { renderAnimate } from "../react/renderers/animate";
|
|
6
|
+
import type { RenderContext } from "../react/renderers/context";
|
|
7
|
+
import { renderImage } from "../react/renderers/image";
|
|
8
|
+
import { renderMusic } from "../react/renderers/music";
|
|
9
|
+
import { createProgressTracker } from "../react/renderers/progress";
|
|
10
|
+
import { renderSpeech } from "../react/renderers/speech";
|
|
11
|
+
import { renderVideo } from "../react/renderers/video";
|
|
12
|
+
import type { RenderProps, VargElement } from "../react/types";
|
|
13
|
+
import type { ExtractedStages, RenderStage, StageResult } from "./stages";
|
|
14
|
+
import { extractStages } from "./stages";
|
|
15
|
+
|
|
16
|
+
export interface StepSession {
|
|
17
|
+
id: string;
|
|
18
|
+
code: string;
|
|
19
|
+
rootElement: VargElement;
|
|
20
|
+
extracted: ExtractedStages;
|
|
21
|
+
ctx: RenderContext;
|
|
22
|
+
results: Map<string, StageResult>;
|
|
23
|
+
currentStageIndex: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const sessions = new Map<string, StepSession>();
|
|
27
|
+
|
|
28
|
+
export function createStepSession(
|
|
29
|
+
code: string,
|
|
30
|
+
rootElement: VargElement,
|
|
31
|
+
cacheDir?: string,
|
|
32
|
+
): StepSession {
|
|
33
|
+
const props = rootElement.props as RenderProps;
|
|
34
|
+
const cache = cacheDir ? fileCache({ dir: cacheDir }) : undefined;
|
|
35
|
+
|
|
36
|
+
const ctx: RenderContext = {
|
|
37
|
+
width: props.width ?? 1920,
|
|
38
|
+
height: props.height ?? 1080,
|
|
39
|
+
fps: props.fps ?? 30,
|
|
40
|
+
cache,
|
|
41
|
+
generateImage: cache
|
|
42
|
+
? withCache(generateImage, { storage: cache })
|
|
43
|
+
: generateImage,
|
|
44
|
+
generateVideo: cache
|
|
45
|
+
? withCache(generateVideo, { storage: cache })
|
|
46
|
+
: generateVideo,
|
|
47
|
+
tempFiles: [],
|
|
48
|
+
progress: createProgressTracker(false),
|
|
49
|
+
pending: new Map(),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const extracted = extractStages(rootElement);
|
|
53
|
+
const sessionId = `step-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
54
|
+
|
|
55
|
+
const session: StepSession = {
|
|
56
|
+
id: sessionId,
|
|
57
|
+
code,
|
|
58
|
+
rootElement,
|
|
59
|
+
extracted,
|
|
60
|
+
ctx,
|
|
61
|
+
results: new Map(),
|
|
62
|
+
currentStageIndex: 0,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
sessions.set(sessionId, session);
|
|
66
|
+
return session;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function getSession(sessionId: string): StepSession | undefined {
|
|
70
|
+
return sessions.get(sessionId);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function deleteSession(sessionId: string): void {
|
|
74
|
+
sessions.delete(sessionId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function executeStage(
|
|
78
|
+
session: StepSession,
|
|
79
|
+
stageId: string,
|
|
80
|
+
): Promise<StageResult> {
|
|
81
|
+
const stage = session.extracted.stages.find((s) => s.id === stageId);
|
|
82
|
+
if (!stage) {
|
|
83
|
+
throw new Error(`Stage ${stageId} not found`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const depId of stage.dependsOn) {
|
|
87
|
+
if (!session.results.has(depId)) {
|
|
88
|
+
throw new Error(`Dependency ${depId} not yet executed`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
stage.status = "running";
|
|
93
|
+
console.log(`[step] executing stage ${stageId}: ${stage.label}`);
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
let result: StageResult;
|
|
97
|
+
|
|
98
|
+
switch (stage.type) {
|
|
99
|
+
case "image": {
|
|
100
|
+
const path = await renderImage(
|
|
101
|
+
stage.element as VargElement<"image">,
|
|
102
|
+
session.ctx,
|
|
103
|
+
);
|
|
104
|
+
result = {
|
|
105
|
+
type: "image",
|
|
106
|
+
path,
|
|
107
|
+
previewUrl: `/api/step/preview/${session.id}/${stageId}`,
|
|
108
|
+
mimeType: "image/png",
|
|
109
|
+
};
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
case "video": {
|
|
114
|
+
const path = await renderVideo(
|
|
115
|
+
stage.element as VargElement<"video">,
|
|
116
|
+
session.ctx,
|
|
117
|
+
);
|
|
118
|
+
result = {
|
|
119
|
+
type: "video",
|
|
120
|
+
path,
|
|
121
|
+
previewUrl: `/api/step/preview/${session.id}/${stageId}`,
|
|
122
|
+
mimeType: "video/mp4",
|
|
123
|
+
};
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
case "animate": {
|
|
128
|
+
const path = await renderAnimate(
|
|
129
|
+
stage.element as VargElement<"animate">,
|
|
130
|
+
session.ctx,
|
|
131
|
+
);
|
|
132
|
+
result = {
|
|
133
|
+
type: "video",
|
|
134
|
+
path,
|
|
135
|
+
previewUrl: `/api/step/preview/${session.id}/${stageId}`,
|
|
136
|
+
mimeType: "video/mp4",
|
|
137
|
+
};
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
case "speech": {
|
|
142
|
+
const speechResult = await renderSpeech(
|
|
143
|
+
stage.element as VargElement<"speech">,
|
|
144
|
+
session.ctx,
|
|
145
|
+
);
|
|
146
|
+
result = {
|
|
147
|
+
type: "audio",
|
|
148
|
+
path: speechResult.path,
|
|
149
|
+
previewUrl: `/api/step/preview/${session.id}/${stageId}`,
|
|
150
|
+
mimeType: "audio/mp3",
|
|
151
|
+
};
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
case "music": {
|
|
156
|
+
const musicResult = await renderMusic(
|
|
157
|
+
stage.element as VargElement<"music">,
|
|
158
|
+
session.ctx,
|
|
159
|
+
);
|
|
160
|
+
result = {
|
|
161
|
+
type: "audio",
|
|
162
|
+
path: musicResult.path,
|
|
163
|
+
previewUrl: `/api/step/preview/${session.id}/${stageId}`,
|
|
164
|
+
mimeType: "audio/mp3",
|
|
165
|
+
};
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
default:
|
|
170
|
+
throw new Error(`Unknown stage type: ${stage.type}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
stage.status = "complete";
|
|
174
|
+
stage.result = result;
|
|
175
|
+
session.results.set(stageId, result);
|
|
176
|
+
console.log(`[step] stage ${stageId} complete: ${result.path}`);
|
|
177
|
+
|
|
178
|
+
return result;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
stage.status = "error";
|
|
181
|
+
console.error(`[step] stage ${stageId} failed:`, error);
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function executeNextStage(session: StepSession): Promise<{
|
|
187
|
+
stage: RenderStage;
|
|
188
|
+
result: StageResult;
|
|
189
|
+
isLast: boolean;
|
|
190
|
+
} | null> {
|
|
191
|
+
const { order } = session.extracted;
|
|
192
|
+
|
|
193
|
+
if (session.currentStageIndex >= order.length) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const stageId = order[session.currentStageIndex];
|
|
198
|
+
if (!stageId) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const stage = session.extracted.stages.find((s) => s.id === stageId);
|
|
203
|
+
if (!stage) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const result = await executeStage(session, stageId);
|
|
208
|
+
session.currentStageIndex++;
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
stage,
|
|
212
|
+
result,
|
|
213
|
+
isLast: session.currentStageIndex >= order.length,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function getStagePreviewPath(
|
|
218
|
+
session: StepSession,
|
|
219
|
+
stageId: string,
|
|
220
|
+
): string | null {
|
|
221
|
+
const result = session.results.get(stageId);
|
|
222
|
+
return result?.path ?? null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export async function finalizeRender(
|
|
226
|
+
session: StepSession,
|
|
227
|
+
outputDir: string,
|
|
228
|
+
): Promise<string> {
|
|
229
|
+
const allComplete = session.extracted.stages.every(
|
|
230
|
+
(s) => s.status === "complete",
|
|
231
|
+
);
|
|
232
|
+
if (!allComplete) {
|
|
233
|
+
throw new Error("Not all stages are complete");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const { render } = await import("../react/render");
|
|
237
|
+
const outputPath = `${outputDir}/render-${session.id}.mp4`;
|
|
238
|
+
|
|
239
|
+
await render(session.rootElement, {
|
|
240
|
+
output: outputPath,
|
|
241
|
+
cache: session.ctx.cache ? ".cache/ai" : undefined,
|
|
242
|
+
quiet: true,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return outputPath;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function getSessionStatus(session: StepSession): {
|
|
249
|
+
sessionId: string;
|
|
250
|
+
totalStages: number;
|
|
251
|
+
completedStages: number;
|
|
252
|
+
currentStageIndex: number;
|
|
253
|
+
stages: Array<{
|
|
254
|
+
id: string;
|
|
255
|
+
type: string;
|
|
256
|
+
label: string;
|
|
257
|
+
status: string;
|
|
258
|
+
hasResult: boolean;
|
|
259
|
+
dependsOn: string[];
|
|
260
|
+
}>;
|
|
261
|
+
} {
|
|
262
|
+
return {
|
|
263
|
+
sessionId: session.id,
|
|
264
|
+
totalStages: session.extracted.stages.length,
|
|
265
|
+
completedStages: session.results.size,
|
|
266
|
+
currentStageIndex: session.currentStageIndex,
|
|
267
|
+
stages: session.extracted.order.map((id) => {
|
|
268
|
+
const stage = session.extracted.stages.find((s) => s.id === id)!;
|
|
269
|
+
return {
|
|
270
|
+
id: stage.id,
|
|
271
|
+
type: stage.type,
|
|
272
|
+
label: stage.label,
|
|
273
|
+
status: stage.status,
|
|
274
|
+
hasResult: session.results.has(id),
|
|
275
|
+
dependsOn: stage.dependsOn,
|
|
276
|
+
};
|
|
277
|
+
}),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface CacheItem {
|
|
2
|
+
id: string;
|
|
3
|
+
filename: string;
|
|
4
|
+
type: "image" | "video" | "unknown";
|
|
5
|
+
size: number;
|
|
6
|
+
createdAt: Date;
|
|
7
|
+
metadata: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CacheEntry {
|
|
11
|
+
value: unknown;
|
|
12
|
+
expires: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ImageData {
|
|
16
|
+
uint8ArrayData?: {
|
|
17
|
+
__type: "Uint8Array";
|
|
18
|
+
data: string;
|
|
19
|
+
};
|
|
20
|
+
base64?: string;
|
|
21
|
+
url?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface VideoData {
|
|
25
|
+
_data?: {
|
|
26
|
+
__type: "Uint8Array";
|
|
27
|
+
data: string;
|
|
28
|
+
};
|
|
29
|
+
uint8ArrayData?: {
|
|
30
|
+
__type: "Uint8Array";
|
|
31
|
+
data: string;
|
|
32
|
+
};
|
|
33
|
+
base64?: string;
|
|
34
|
+
url?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface RenderRequest {
|
|
38
|
+
code: string;
|
|
39
|
+
cacheDir?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface RenderProgress {
|
|
43
|
+
step: "parsing" | "rendering" | "complete" | "error";
|
|
44
|
+
progress: number;
|
|
45
|
+
message: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface Template {
|
|
49
|
+
id: string;
|
|
50
|
+
name: string;
|
|
51
|
+
description: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface HistoryItem {
|
|
55
|
+
id: string;
|
|
56
|
+
code: string;
|
|
57
|
+
videoUrl: string;
|
|
58
|
+
thumbnail?: string;
|
|
59
|
+
createdAt: Date;
|
|
60
|
+
}
|