@effectnode/media 0.1.0

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 (133) hide show
  1. package/bin/effectnode-media +2 -0
  2. package/dist/backend/index.d.ts +8 -0
  3. package/dist/backend/index.js +13 -0
  4. package/dist/backend/movie-backend/agent/agent-backend.d.ts +16 -0
  5. package/dist/backend/movie-backend/agent/agent-backend.js +703 -0
  6. package/dist/backend/movie-backend/agent/storyprompt.d.ts +1 -0
  7. package/dist/backend/movie-backend/agent/storyprompt.js +90 -0
  8. package/dist/backend/movie-backend/agent/tools/edit-image.d.ts +3 -0
  9. package/dist/backend/movie-backend/agent/tools/edit-image.js +192 -0
  10. package/dist/backend/movie-backend/agent/tools/get-time.d.ts +3 -0
  11. package/dist/backend/movie-backend/agent/tools/get-time.js +7 -0
  12. package/dist/backend/movie-backend/agent/tools/grep-files.d.ts +3 -0
  13. package/dist/backend/movie-backend/agent/tools/grep-files.js +59 -0
  14. package/dist/backend/movie-backend/agent/tools/image-to-video-generation.d.ts +3 -0
  15. package/dist/backend/movie-backend/agent/tools/image-to-video-generation.js +160 -0
  16. package/dist/backend/movie-backend/agent/tools/index.d.ts +7 -0
  17. package/dist/backend/movie-backend/agent/tools/index.js +59 -0
  18. package/dist/backend/movie-backend/agent/tools/list-files.d.ts +3 -0
  19. package/dist/backend/movie-backend/agent/tools/list-files.js +17 -0
  20. package/dist/backend/movie-backend/agent/tools/read-file.d.ts +3 -0
  21. package/dist/backend/movie-backend/agent/tools/read-file.js +28 -0
  22. package/dist/backend/movie-backend/agent/tools/remove-file.d.ts +3 -0
  23. package/dist/backend/movie-backend/agent/tools/remove-file.js +22 -0
  24. package/dist/backend/movie-backend/agent/tools/rename-file.d.ts +3 -0
  25. package/dist/backend/movie-backend/agent/tools/rename-file.js +37 -0
  26. package/dist/backend/movie-backend/agent/tools/show-image.d.ts +3 -0
  27. package/dist/backend/movie-backend/agent/tools/show-image.js +28 -0
  28. package/dist/backend/movie-backend/agent/tools/stitch-videos.d.ts +3 -0
  29. package/dist/backend/movie-backend/agent/tools/stitch-videos.js +96 -0
  30. package/dist/backend/movie-backend/agent/tools/text-to-video-generation.d.ts +3 -0
  31. package/dist/backend/movie-backend/agent/tools/text-to-video-generation.js +125 -0
  32. package/dist/backend/movie-backend/agent/tools/types.d.ts +15 -0
  33. package/dist/backend/movie-backend/agent/tools/types.js +1 -0
  34. package/dist/backend/movie-backend/agent/tools/update-file.d.ts +3 -0
  35. package/dist/backend/movie-backend/agent/tools/update-file.js +27 -0
  36. package/dist/backend/movie-backend/agent/tools/write-file.d.ts +3 -0
  37. package/dist/backend/movie-backend/agent/tools/write-file.js +26 -0
  38. package/dist/backend/movie-backend/agent/workspace.d.ts +25 -0
  39. package/dist/backend/movie-backend/agent/workspace.js +124 -0
  40. package/dist/backend/movie-backend/core.d.ts +4 -0
  41. package/dist/backend/movie-backend/core.js +1167 -0
  42. package/dist/backend/movie-backend/generation-queue.d.ts +24 -0
  43. package/dist/backend/movie-backend/generation-queue.js +766 -0
  44. package/dist/backend/movie-backend/process.d.ts +21 -0
  45. package/dist/backend/movie-backend/process.js +98 -0
  46. package/dist/backend/movie-backend/render-media.d.ts +48 -0
  47. package/dist/backend/movie-backend/render-media.js +3311 -0
  48. package/dist/backend/routes/health.d.ts +1 -0
  49. package/dist/backend/routes/health.js +9 -0
  50. package/dist/backend/routes/index.d.ts +1 -0
  51. package/dist/backend/routes/index.js +11 -0
  52. package/dist/backend/routes/media.d.ts +1 -0
  53. package/dist/backend/routes/media.js +13 -0
  54. package/dist/backend/routes/projects.d.ts +8 -0
  55. package/dist/backend/routes/projects.js +68 -0
  56. package/dist/backend/routes/voice.d.ts +1 -0
  57. package/dist/backend/routes/voice.js +39 -0
  58. package/dist/backend/routes.d.ts +1 -0
  59. package/dist/backend/routes.js +20 -0
  60. package/dist/backend/server.d.ts +1 -0
  61. package/dist/backend/server.js +23 -0
  62. package/dist/backend/workspace.d.ts +5 -0
  63. package/dist/backend/workspace.js +27 -0
  64. package/dist/backend/ws/index.d.ts +3 -0
  65. package/dist/backend/ws/index.js +27 -0
  66. package/dist/backend/ws/messages.d.ts +8 -0
  67. package/dist/backend/ws/messages.js +21 -0
  68. package/dist/backend/ws.d.ts +3 -0
  69. package/dist/backend/ws.js +29 -0
  70. package/dist/index.d.ts +2 -0
  71. package/dist/index.js +80 -0
  72. package/frontend/index.html +22 -0
  73. package/frontend/public/ffmpeg/ffmpeg-core.js +16 -0
  74. package/frontend/public/ffmpeg/ffmpeg-core.wasm +0 -0
  75. package/frontend/src/index.css +46 -0
  76. package/frontend/src/main.tsx +13 -0
  77. package/frontend/src/movie-app/AppRouter.tsx +14 -0
  78. package/frontend/src/movie-app/MediaStudio.tsx +9 -0
  79. package/frontend/src/movie-app/SetupPage.tsx +293 -0
  80. package/frontend/src/movie-app/components/AgentWorkspace.tsx +574 -0
  81. package/frontend/src/movie-app/components/ChatUI/ChatUI.tsx +655 -0
  82. package/frontend/src/movie-app/components/ChatUI/MarkdownMessage.tsx +267 -0
  83. package/frontend/src/movie-app/components/EditorTabs/AgentTab.tsx +61 -0
  84. package/frontend/src/movie-app/components/EditorTabs/BatchImageToVideoTab.tsx +927 -0
  85. package/frontend/src/movie-app/components/EditorTabs/BatchVideoTab.tsx +777 -0
  86. package/frontend/src/movie-app/components/EditorTabs/BatchVoiceVideoTab.tsx +913 -0
  87. package/frontend/src/movie-app/components/EditorTabs/CharacterSheet.tsx +234 -0
  88. package/frontend/src/movie-app/components/EditorTabs/CharactersTab.tsx +664 -0
  89. package/frontend/src/movie-app/components/EditorTabs/CropTool.tsx +251 -0
  90. package/frontend/src/movie-app/components/EditorTabs/ExtendVideoTab.tsx +305 -0
  91. package/frontend/src/movie-app/components/EditorTabs/ExtractImageTab.tsx +249 -0
  92. package/frontend/src/movie-app/components/EditorTabs/FastImageEditTab.tsx +555 -0
  93. package/frontend/src/movie-app/components/EditorTabs/GenerateVideoTab.tsx +722 -0
  94. package/frontend/src/movie-app/components/EditorTabs/LlmServerTab.tsx +40 -0
  95. package/frontend/src/movie-app/components/EditorTabs/MlxVlmServerPanel.tsx +490 -0
  96. package/frontend/src/movie-app/components/EditorTabs/MovieStudioTab.tsx +938 -0
  97. package/frontend/src/movie-app/components/EditorTabs/ReferencesToVideoTab.tsx +881 -0
  98. package/frontend/src/movie-app/components/EditorTabs/SceneVisualTab.tsx +267 -0
  99. package/frontend/src/movie-app/components/EditorTabs/StoryWriterTab.tsx +51 -0
  100. package/frontend/src/movie-app/components/EditorTabs/TaskQueuePanel.tsx +246 -0
  101. package/frontend/src/movie-app/components/EditorTabs/TerminalLogPanel.tsx +49 -0
  102. package/frontend/src/movie-app/components/EditorTabs/TextToImageTab.tsx +574 -0
  103. package/frontend/src/movie-app/components/EditorTabs/VoiceChatPanel.tsx +320 -0
  104. package/frontend/src/movie-app/components/ProjectEditorPage.tsx +636 -0
  105. package/frontend/src/movie-app/components/ProjectManager.tsx +381 -0
  106. package/frontend/src/movie-app/index.css +46 -0
  107. package/frontend/src/movie-app/index.html +16 -0
  108. package/frontend/src/movie-app/lambobo.png +0 -0
  109. package/frontend/src/movie-app/lib/batchImageToVideoStorage.ts +69 -0
  110. package/frontend/src/movie-app/lib/batchVideoStorage.ts +68 -0
  111. package/frontend/src/movie-app/lib/batchVoiceStorage.ts +75 -0
  112. package/frontend/src/movie-app/lib/ffmpeg.ts +42 -0
  113. package/frontend/src/movie-app/lib/referencesToVideoStorage.ts +64 -0
  114. package/frontend/src/movie-app/lib/textToImageStorage.ts +61 -0
  115. package/frontend/src/movie-app/public/ffmpeg/ffmpeg-core.js +16 -0
  116. package/frontend/src/movie-app/public/ffmpeg/ffmpeg-core.wasm +0 -0
  117. package/frontend/src/movie-app/stores/batchImageToVideoStore.ts +736 -0
  118. package/frontend/src/movie-app/stores/batchVideoStore.ts +670 -0
  119. package/frontend/src/movie-app/stores/batchVoiceStore.ts +990 -0
  120. package/frontend/src/movie-app/stores/characterStore.ts +109 -0
  121. package/frontend/src/movie-app/stores/chatStore.ts +459 -0
  122. package/frontend/src/movie-app/stores/generationStore.ts +2140 -0
  123. package/frontend/src/movie-app/stores/logStore.ts +25 -0
  124. package/frontend/src/movie-app/stores/movieStudioStore.ts +709 -0
  125. package/frontend/src/movie-app/stores/projectStore.ts +124 -0
  126. package/frontend/src/movie-app/stores/queueStore.ts +214 -0
  127. package/frontend/src/movie-app/stores/referencesToVideoStore.ts +467 -0
  128. package/frontend/src/movie-app/stores/sceneVisualStore.ts +251 -0
  129. package/frontend/src/movie-app/stores/voiceChatStore.ts +170 -0
  130. package/frontend/src/movie-app/stores/workspaceStore.ts +158 -0
  131. package/frontend/src/vite-env.d.ts +1 -0
  132. package/frontend/tsconfig.json +19 -0
  133. package/package.json +65 -0
@@ -0,0 +1 @@
1
+ export declare const storyPrompt = "# Vid Script Builder\n\n## Role & Purpose\n\nYou are a short-form video planner and AI Prompt Specialist specializing in humorous, viral content. Your mission is to take user-provided video topics, comedy concepts, or scenarios and convert them directly into structured CSV data designed for Text-to-Image (T2I) and Image-to-Video (I2V) production workflows. Each row represents a single moment of a camera shot, with its own duration (3\u201320 seconds).\n\n## Workflow & Output Rules\n\nWhen a user provides a theme, scenario, or prompt request, adhere strictly to the following specifications.\n\n### 1. Table Schema (CSV Format)\n\nEvery output must include the following four columns:\n\n| Column | Description |\n| ---------- | ----------------------------------------- |\n| `id` | Sequential moment number (1, 2, 3...) |\n| `duration` | Duration of this moment in seconds (3\u201320) |\n| `t2i` | Text-to-Image Prompt (English) |\n| `i2v` | Image-to-Video Prompt (English) |\n\n### 2. Scene Breakdown & Segmentation Constraint\n\n- **Each item is a single moment of a camera shot.** Treat every row as one discrete moment/beat within a shot \u2014 not a whole scene.\n- **Break large scenes into many small moments.** When a user provides a broad scene, storyline, or concept, decompose it into as many single-moment rows as possible \u2014 never compress a large scene into one row.\n- Each row carries a `duration` between **3 and 20 seconds**, but **prefer small moments**: break the action into the smallest coherent beats and favor short durations (3\u20136 seconds); use a longer duration only when one continuous camera move or action genuinely requires it.\n- Each moment captures exactly one action beat, one camera setup, and one comedic/punchline step.\n- Maintain narrative continuity across consecutive moments (consistent characters, setting, and progression) so the sequence edits together into one coherent video.\n- Pacing, narrative action, character dialogue, and voiceover in each moment must be calibrated to fit strictly within its stated duration.\n\n### 3. t2i (Text-to-Image) Guidelines \u2014 Z-Image-Turbo\n\n- **Language:** English.\n- **Model:** Optimized for **Z-Image-Turbo**. Write prompts as **natural-language sentences**, never comma-separated keyword tags (`(masterpiece), 8k, trending on artstation` fails).\n- **Subject first:** Lead with the subject and its action \u2014 Z-Image-Turbo weights earlier tokens more heavily, so never bury the main subject late in the description.\n- **Character consistency (critical across shots):** before generating any prompts, write one fixed, detailed character description (a \"character bible\") for every recurring character \u2014 name, age, face shape, hair color/style, build, outfit, and any distinctive feature (scar, glasses, earring). Repeat that exact description verbatim at the start of every t2i prompt featuring that character, varying only the shot-specific action, expression, and pose. Never reword or omit core appearance details between shots, or the character's identity will drift across scenes.\n- **One focused paragraph** built in this layered order:\n 1. **Subject & Action** \u2014 who/what, doing what, with specific physical details.\n 2. **Environment & Context** \u2014 location, time of day, weather, background.\n 3. **Lighting & Atmosphere** \u2014 the strongest lever after style.\n 4. **Visual Style** \u2014 pick **one** primary style (e.g., cinematic photorealistic) plus camera/lens/film details.\n 5. **Composition** \u2014 framing, focus, close-up vs. wide shot.\n- **No negative prompts:** Z-Image-Turbo has no classifier-free guidance, so negative prompts are unsupported. Phrase constraints positively (e.g., \"a quiet, deserted street\" instead of \"no people\").\n- **Be specific, avoid vagueness:** replace \"a dog\" with \"a golden retriever puppy with one ear flopped sideways, sitting on a porch step.\"\n- **Keep quality modifiers minimal:** 2\u20133 technical tags max (e.g., \"85mm f/1.8, shallow depth of field\").\n- **On-image text (if any):** wrap literal text in straight quotes, keep it to a short phrase, and specify font style and placement.\n- **Scope:** each prompt must describe a single moment of a camera shot, matching its stated duration.\n\n### 4. i2v (Image-to-Video) Guidelines \u2014 LTX-2.3\n\n- **Language:** English.\n- **Core rule \u2014 describe the motion, not the image.** The t2i image is the anchor that already fixes the subject, setting, and lighting. Do not re-describe static details; write only what changes: motion, camera movement, and performance.\n- **Motion-first:** lead with action verbs and camera verbs in the first 20\u201340 words. Describe \"how the pixels move,\" not the subject itself.\n- **Scale motion to the moment's duration:** one main action beat plus one simple camera move for short moments (3\u20136s); add a second beat or a longer continuous move only when the duration exceeds ~10s. Do not stack simultaneous motions, and avoid complex camera terms (e.g., \"rack focus\", \"dolly zoom\") that the distilled model drifts on.\n- **Avoid:** generic phrasing (\"make it look nice\"), contradictory directions (\"fast dramatic zoom but calm and slow\"), still-photo phrasing (\"preserve identity\", \"gentle parallax\"), and prompts over ~150 words.\n- **Never** include the clip duration or the model name in the prompt text.\n- **Negative prompt:** keep negatives in the dedicated negative field (e.g., `no stiff motion, no flat lighting, no artifacts, no morphing, no flickering`), never inside the positive prompt.\n\nEvery i2v prompt must still explicitly include these four components, written as a single cinematic paragraph:\n\n| # | Component | Description |\n| --- | ------------------- | ----------------------------------------------------------------------------------------- |\n| 1 | Camera Movement | One specific motion directive (e.g., slow dolly-in, steady hold, snap zoom). |\n| 2 | Character Name | Named subject performing the action \u2014 name once, then focus on their motion/performance. |\n| 3 | Character Dialogue | Spoken lines in quotation marks, treated as an audio/lipsync cue. |\n| 4 | Voiceover/Narration | Formatted as `Voiceover: \"...\"` to capture the punchline, inner monologue, or core humor. |\n\n## Output Format Standard\n\nOutput the raw content inside a clean CSV code block, followed immediately by a rendered Markdown preview table.\n\n```csv\nid,duration,t2i,i2v\n1,5,\"[English T2I Prompt]\",\"[English I2V Prompt: Includes camera movement, character name, dialogue, and voiceover narration structured for this moment's duration]\"\n```\n\n### Sample Character Bible (reused across every shot)\n\n> Alex \u2014 a 20-year-old male college student with short black hair, round wire-frame glasses, and a faded blue hoodie, always with a nervous, wide-eyed look. Repeat this exact description verbatim at the start of every t2i prompt that features Alex, changing only his action and expression per moment.\n\n### Sample t2i Prompt Structure (single moment)\n\n> A close-up of Alex, a college student with wide, panicked eyes and sweat beading on his forehead, frantically gripping a pen in an exam hall. Harsh fluorescent overhead lighting, rows of wooden desks receding into the background, other students blurred in soft focus. Cinematic photorealistic style, shot on a 50mm lens at f/2.0, shallow depth of field, tense anxious atmosphere.\n\n### Sample i2v Prompt Structure (single moment)\n\n> Slow dolly-in toward Alex's face as his eyes widen and a bead of sweat rolls down his temple. He grips the pen tighter and whispers urgently: \"I'm so dead, I literally haven't opened the textbook!\" Voiceover: \"Every class has that one top student whose catchphrase is 'I failed,' right before scoring 100%.\"\n\n> **Negative prompt:** `no stiff motion, no flat lighting, no artifacts, no morphing, no flickering`\n";
@@ -0,0 +1,90 @@
1
+ export const storyPrompt = `# Vid Script Builder
2
+
3
+ ## Role & Purpose
4
+
5
+ You are a short-form video planner and AI Prompt Specialist specializing in humorous, viral content. Your mission is to take user-provided video topics, comedy concepts, or scenarios and convert them directly into structured CSV data designed for Text-to-Image (T2I) and Image-to-Video (I2V) production workflows. Each row represents a single moment of a camera shot, with its own duration (3–20 seconds).
6
+
7
+ ## Workflow & Output Rules
8
+
9
+ When a user provides a theme, scenario, or prompt request, adhere strictly to the following specifications.
10
+
11
+ ### 1. Table Schema (CSV Format)
12
+
13
+ Every output must include the following four columns:
14
+
15
+ | Column | Description |
16
+ | ---------- | ----------------------------------------- |
17
+ | \`id\` | Sequential moment number (1, 2, 3...) |
18
+ | \`duration\` | Duration of this moment in seconds (3–20) |
19
+ | \`t2i\` | Text-to-Image Prompt (English) |
20
+ | \`i2v\` | Image-to-Video Prompt (English) |
21
+
22
+ ### 2. Scene Breakdown & Segmentation Constraint
23
+
24
+ - **Each item is a single moment of a camera shot.** Treat every row as one discrete moment/beat within a shot — not a whole scene.
25
+ - **Break large scenes into many small moments.** When a user provides a broad scene, storyline, or concept, decompose it into as many single-moment rows as possible — never compress a large scene into one row.
26
+ - Each row carries a \`duration\` between **3 and 20 seconds**, but **prefer small moments**: break the action into the smallest coherent beats and favor short durations (3–6 seconds); use a longer duration only when one continuous camera move or action genuinely requires it.
27
+ - Each moment captures exactly one action beat, one camera setup, and one comedic/punchline step.
28
+ - Maintain narrative continuity across consecutive moments (consistent characters, setting, and progression) so the sequence edits together into one coherent video.
29
+ - Pacing, narrative action, character dialogue, and voiceover in each moment must be calibrated to fit strictly within its stated duration.
30
+
31
+ ### 3. t2i (Text-to-Image) Guidelines — Z-Image-Turbo
32
+
33
+ - **Language:** English.
34
+ - **Model:** Optimized for **Z-Image-Turbo**. Write prompts as **natural-language sentences**, never comma-separated keyword tags (\`(masterpiece), 8k, trending on artstation\` fails).
35
+ - **Subject first:** Lead with the subject and its action — Z-Image-Turbo weights earlier tokens more heavily, so never bury the main subject late in the description.
36
+ - **Character consistency (critical across shots):** before generating any prompts, write one fixed, detailed character description (a "character bible") for every recurring character — name, age, face shape, hair color/style, build, outfit, and any distinctive feature (scar, glasses, earring). Repeat that exact description verbatim at the start of every t2i prompt featuring that character, varying only the shot-specific action, expression, and pose. Never reword or omit core appearance details between shots, or the character's identity will drift across scenes.
37
+ - **One focused paragraph** built in this layered order:
38
+ 1. **Subject & Action** — who/what, doing what, with specific physical details.
39
+ 2. **Environment & Context** — location, time of day, weather, background.
40
+ 3. **Lighting & Atmosphere** — the strongest lever after style.
41
+ 4. **Visual Style** — pick **one** primary style (e.g., cinematic photorealistic) plus camera/lens/film details.
42
+ 5. **Composition** — framing, focus, close-up vs. wide shot.
43
+ - **No negative prompts:** Z-Image-Turbo has no classifier-free guidance, so negative prompts are unsupported. Phrase constraints positively (e.g., "a quiet, deserted street" instead of "no people").
44
+ - **Be specific, avoid vagueness:** replace "a dog" with "a golden retriever puppy with one ear flopped sideways, sitting on a porch step."
45
+ - **Keep quality modifiers minimal:** 2–3 technical tags max (e.g., "85mm f/1.8, shallow depth of field").
46
+ - **On-image text (if any):** wrap literal text in straight quotes, keep it to a short phrase, and specify font style and placement.
47
+ - **Scope:** each prompt must describe a single moment of a camera shot, matching its stated duration.
48
+
49
+ ### 4. i2v (Image-to-Video) Guidelines — LTX-2.3
50
+
51
+ - **Language:** English.
52
+ - **Core rule — describe the motion, not the image.** The t2i image is the anchor that already fixes the subject, setting, and lighting. Do not re-describe static details; write only what changes: motion, camera movement, and performance.
53
+ - **Motion-first:** lead with action verbs and camera verbs in the first 20–40 words. Describe "how the pixels move," not the subject itself.
54
+ - **Scale motion to the moment's duration:** one main action beat plus one simple camera move for short moments (3–6s); add a second beat or a longer continuous move only when the duration exceeds ~10s. Do not stack simultaneous motions, and avoid complex camera terms (e.g., "rack focus", "dolly zoom") that the distilled model drifts on.
55
+ - **Avoid:** generic phrasing ("make it look nice"), contradictory directions ("fast dramatic zoom but calm and slow"), still-photo phrasing ("preserve identity", "gentle parallax"), and prompts over ~150 words.
56
+ - **Never** include the clip duration or the model name in the prompt text.
57
+ - **Negative prompt:** keep negatives in the dedicated negative field (e.g., \`no stiff motion, no flat lighting, no artifacts, no morphing, no flickering\`), never inside the positive prompt.
58
+
59
+ Every i2v prompt must still explicitly include these four components, written as a single cinematic paragraph:
60
+
61
+ | # | Component | Description |
62
+ | --- | ------------------- | ----------------------------------------------------------------------------------------- |
63
+ | 1 | Camera Movement | One specific motion directive (e.g., slow dolly-in, steady hold, snap zoom). |
64
+ | 2 | Character Name | Named subject performing the action — name once, then focus on their motion/performance. |
65
+ | 3 | Character Dialogue | Spoken lines in quotation marks, treated as an audio/lipsync cue. |
66
+ | 4 | Voiceover/Narration | Formatted as \`Voiceover: "..."\` to capture the punchline, inner monologue, or core humor. |
67
+
68
+ ## Output Format Standard
69
+
70
+ Output the raw content inside a clean CSV code block, followed immediately by a rendered Markdown preview table.
71
+
72
+ \`\`\`csv
73
+ id,duration,t2i,i2v
74
+ 1,5,"[English T2I Prompt]","[English I2V Prompt: Includes camera movement, character name, dialogue, and voiceover narration structured for this moment's duration]"
75
+ \`\`\`
76
+
77
+ ### Sample Character Bible (reused across every shot)
78
+
79
+ > Alex — a 20-year-old male college student with short black hair, round wire-frame glasses, and a faded blue hoodie, always with a nervous, wide-eyed look. Repeat this exact description verbatim at the start of every t2i prompt that features Alex, changing only his action and expression per moment.
80
+
81
+ ### Sample t2i Prompt Structure (single moment)
82
+
83
+ > A close-up of Alex, a college student with wide, panicked eyes and sweat beading on his forehead, frantically gripping a pen in an exam hall. Harsh fluorescent overhead lighting, rows of wooden desks receding into the background, other students blurred in soft focus. Cinematic photorealistic style, shot on a 50mm lens at f/2.0, shallow depth of field, tense anxious atmosphere.
84
+
85
+ ### Sample i2v Prompt Structure (single moment)
86
+
87
+ > Slow dolly-in toward Alex's face as his eyes widen and a bead of sweat rolls down his temple. He grips the pen tighter and whispers urgently: "I'm so dead, I literally haven't opened the textbook!" Voiceover: "Every class has that one top student whose catchphrase is 'I failed,' right before scoring 100%."
88
+
89
+ > **Negative prompt:** \`no stiff motion, no flat lighting, no artifacts, no morphing, no flickering\`
90
+ `;
@@ -0,0 +1,3 @@
1
+ import type { AgentTool } from "./types";
2
+ declare const tool: AgentTool;
3
+ export default tool;
@@ -0,0 +1,192 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, realpathSync, } from "node:fs";
2
+ import { join, sep } from "node:path";
3
+ import { isValidProjectId, resolveWorkspacePath, classifyFile, AGENT_UPLOAD_DIR, workspaceDir, } from "../workspace";
4
+ import sharp from "sharp";
5
+ /** Default output resolution (px) when the tool is called without one. */
6
+ const DEFAULT_RESOLUTION = 512;
7
+ /** Allowed resolution values (px). */
8
+ const ALLOWED_RESOLUTIONS = [512, 1024];
9
+ /** Resize an image to at most `maxDim` on the longest edge, keeping aspect ratio. */
10
+ async function resizeToMax(input, maxDim) {
11
+ const resized = await sharp(input)
12
+ .resize({
13
+ width: maxDim,
14
+ height: maxDim,
15
+ fit: "inside",
16
+ withoutEnlargement: true,
17
+ })
18
+ .png()
19
+ .toBuffer();
20
+ const meta = await sharp(resized).metadata();
21
+ return {
22
+ buffer: resized,
23
+ width: meta.width ?? maxDim,
24
+ height: meta.height ?? maxDim,
25
+ };
26
+ }
27
+ async function readSSE(response, onEvent) {
28
+ const reader = response.body?.getReader();
29
+ if (!reader)
30
+ return;
31
+ const decoder = new TextDecoder();
32
+ let buffer = "";
33
+ try {
34
+ while (true) {
35
+ const { done, value } = await reader.read();
36
+ if (done)
37
+ break;
38
+ buffer += decoder.decode(value, { stream: true });
39
+ const lines = buffer.split("\n");
40
+ buffer = lines.pop() || "";
41
+ let eventType = "message";
42
+ for (const line of lines) {
43
+ if (line.startsWith("event: ")) {
44
+ eventType = line.slice(7).trim();
45
+ }
46
+ else if (line.startsWith("data: ")) {
47
+ try {
48
+ onEvent(eventType, JSON.parse(line.slice(6)));
49
+ }
50
+ catch {
51
+ // skip malformed lines
52
+ }
53
+ eventType = "message";
54
+ }
55
+ }
56
+ }
57
+ }
58
+ finally {
59
+ reader.releaseLock();
60
+ }
61
+ }
62
+ const tool = {
63
+ name: "edit_image",
64
+ description: "Edit an image in the workspace using a text prompt (e.g. change the background, style, or expression).",
65
+ parameters: {
66
+ type: "object",
67
+ properties: {
68
+ prompt: { type: "string", description: "The edit instruction" },
69
+ image: {
70
+ type: "string",
71
+ description: "Path of the image in the workspace to edit",
72
+ },
73
+ resolution: {
74
+ type: "number",
75
+ enum: [512, 1024],
76
+ description: "Output resolution in pixels (512 or 1024, default 512)",
77
+ },
78
+ },
79
+ required: ["prompt", "image"],
80
+ },
81
+ run: async (args, ctx) => {
82
+ if (!isValidProjectId(ctx.projectId))
83
+ return "Invalid project ID.";
84
+ const prompt = typeof args.prompt === "string" ? args.prompt.trim() : "";
85
+ if (!prompt)
86
+ return "edit_image requires a prompt.";
87
+ const image = typeof args.image === "string" ? args.image.trim() : "";
88
+ if (!image)
89
+ return "edit_image requires an image from the workspace.";
90
+ const resolution = typeof args.resolution === "number" &&
91
+ ALLOWED_RESOLUTIONS.includes(args.resolution)
92
+ ? args.resolution
93
+ : DEFAULT_RESOLUTION;
94
+ const abs = resolveWorkspacePath(ctx.projectId, image);
95
+ if (!abs || !existsSync(abs)) {
96
+ return `Image not found: ${image}`;
97
+ }
98
+ // Classify the resolved real path (resolves symlinks) so a symlink named
99
+ // like an image but pointing at another file is not treated as an image.
100
+ let realAbs;
101
+ try {
102
+ realAbs = realpathSync(abs);
103
+ }
104
+ catch {
105
+ return `Image not found: ${image}`;
106
+ }
107
+ // Confirm the resolved (symlink-followed) path is still inside the
108
+ // project's workspace before reading it.
109
+ const realBase = realpathSync(workspaceDir(ctx.projectId));
110
+ if (realAbs !== realBase && !realAbs.startsWith(realBase + sep)) {
111
+ return `Image outside workspace: ${image}`;
112
+ }
113
+ if (classifyFile(realAbs) !== "image") {
114
+ return `Not an image: ${image}`;
115
+ }
116
+ if (!ctx.backendPort)
117
+ return "Image backend not available.";
118
+ // Resize the source image to at most the chosen resolution (keeping aspect
119
+ // ratio) and re-encode as PNG, then stage it into the project's
120
+ // agent-upload dir so the mlxgen endpoint can resolve it by bare filename.
121
+ let resized;
122
+ try {
123
+ resized = await resizeToMax(readFileSync(realAbs), resolution);
124
+ }
125
+ catch (e) {
126
+ return `Failed to process image: ${String(e)}`;
127
+ }
128
+ const uploadProjectDir = join(AGENT_UPLOAD_DIR, ctx.projectId);
129
+ if (!existsSync(uploadProjectDir)) {
130
+ mkdirSync(uploadProjectDir, { recursive: true });
131
+ }
132
+ const origBase = (image.split("/").pop() || "image.png").replace(/\.[^.]+$/, "");
133
+ const imageName = `agent-${Date.now()}-${origBase.replace(/[^a-zA-Z0-9._-]/g, "_")}.png`;
134
+ writeFileSync(join(uploadProjectDir, imageName), resized.buffer);
135
+ ctx.emit?.("notice", { text: `Editing image "${image}"...` });
136
+ try {
137
+ const body = {
138
+ prompt,
139
+ imagePath: imageName,
140
+ projectId: ctx.projectId,
141
+ width: resized.width,
142
+ height: resized.height,
143
+ };
144
+ const res = await fetch(`http://localhost:${ctx.backendPort}/api/mlxgen/generate`, {
145
+ method: "POST",
146
+ headers: { "Content-Type": "application/json" },
147
+ body: JSON.stringify(body),
148
+ signal: ctx.signal,
149
+ });
150
+ if (!res.ok) {
151
+ return `Image edit failed: ${await res.text()}`;
152
+ }
153
+ let resultPath = null;
154
+ let resultFilename = null;
155
+ let error = null;
156
+ await readSSE(res, (event, data) => {
157
+ switch (event) {
158
+ case "log":
159
+ ctx.emit?.("notice", { text: data.text });
160
+ break;
161
+ case "complete":
162
+ resultPath = data.path;
163
+ resultFilename = data.filename;
164
+ break;
165
+ case "error":
166
+ error = data.error || "Image edit failed";
167
+ break;
168
+ }
169
+ });
170
+ if (error)
171
+ return error;
172
+ if (!resultPath)
173
+ return "Image edit did not produce a result.";
174
+ // Copy the edited image into the workspace so it is previewable and
175
+ // usable in subsequent steps.
176
+ const outName = resultFilename ||
177
+ resultPath.split("/").pop() ||
178
+ "edited.png";
179
+ const outDir = workspaceDir(ctx.projectId);
180
+ if (!existsSync(outDir))
181
+ mkdirSync(outDir, { recursive: true });
182
+ writeFileSync(join(outDir, outName), readFileSync(resultPath));
183
+ const url = `/api/agent/file/preview?projectId=${encodeURIComponent(ctx.projectId)}&path=${encodeURIComponent(outName)}`;
184
+ ctx.emit?.("image", { url, path: outName });
185
+ return `Edited image saved to "${outName}".`;
186
+ }
187
+ catch (e) {
188
+ return `Image edit error: ${String(e)}`;
189
+ }
190
+ },
191
+ };
192
+ export default tool;
@@ -0,0 +1,3 @@
1
+ import type { AgentTool } from "./types";
2
+ declare const tool: AgentTool;
3
+ export default tool;
@@ -0,0 +1,7 @@
1
+ const tool = {
2
+ name: "get_time",
3
+ description: "Return the current date and time.",
4
+ parameters: { type: "object", properties: {}, additionalProperties: false },
5
+ run: () => new Date().toString(),
6
+ };
7
+ export default tool;
@@ -0,0 +1,3 @@
1
+ import type { AgentTool } from "./types";
2
+ declare const tool: AgentTool;
3
+ export default tool;
@@ -0,0 +1,59 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { resolveWorkspacePath, workspaceDir, walkFiles } from "../workspace";
4
+ const tool = {
5
+ name: "grep_files",
6
+ description: "Search for a regex pattern within text files in the workspace.",
7
+ parameters: {
8
+ type: "object",
9
+ properties: {
10
+ pattern: { type: "string", description: "Regular expression to search for" },
11
+ path: {
12
+ type: "string",
13
+ description: "Optional directory to search (defaults to the whole workspace)",
14
+ },
15
+ },
16
+ required: ["pattern"],
17
+ },
18
+ run: (args, ctx) => {
19
+ const pattern = typeof args.pattern === "string" ? args.pattern : "";
20
+ if (!pattern)
21
+ return "grep_files requires a pattern.";
22
+ let re;
23
+ try {
24
+ re = new RegExp(pattern, "i");
25
+ }
26
+ catch {
27
+ return `Invalid regex: ${pattern}`;
28
+ }
29
+ const dirArg = typeof args.path === "string" && args.path.trim() ? args.path.trim() : "";
30
+ const base = dirArg
31
+ ? resolveWorkspacePath(ctx.projectId, dirArg)
32
+ : workspaceDir(ctx.projectId);
33
+ if (!base || !existsSync(base)) {
34
+ return `Directory not found: ${dirArg || "(workspace)"}`;
35
+ }
36
+ const results = [];
37
+ for (const f of walkFiles(base)) {
38
+ if (f.kind !== "text")
39
+ continue;
40
+ const abs = join(base, f.path);
41
+ let content;
42
+ try {
43
+ content = readFileSync(abs, "utf-8");
44
+ }
45
+ catch {
46
+ continue;
47
+ }
48
+ content.split("\n").forEach((line, i) => {
49
+ if (re.test(line)) {
50
+ results.push(`${f.path}:${i + 1}: ${line.trim()}`);
51
+ }
52
+ });
53
+ }
54
+ return results.length > 0
55
+ ? results.join("\n")
56
+ : `No matches for "${pattern}".`;
57
+ },
58
+ };
59
+ export default tool;
@@ -0,0 +1,3 @@
1
+ import type { AgentTool } from "./types";
2
+ declare const tool: AgentTool;
3
+ export default tool;
@@ -0,0 +1,160 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, realpathSync, } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { isValidProjectId, resolveWorkspacePath, classifyFile, AGENT_UPLOAD_DIR, workspaceDir, } from "../workspace";
4
+ async function readSSE(response, onEvent) {
5
+ const reader = response.body?.getReader();
6
+ if (!reader)
7
+ return;
8
+ const decoder = new TextDecoder();
9
+ let buffer = "";
10
+ try {
11
+ while (true) {
12
+ const { done, value } = await reader.read();
13
+ if (done)
14
+ break;
15
+ buffer += decoder.decode(value, { stream: true });
16
+ const lines = buffer.split("\n");
17
+ buffer = lines.pop() || "";
18
+ let eventType = "message";
19
+ for (const line of lines) {
20
+ if (line.startsWith("event: ")) {
21
+ eventType = line.slice(7).trim();
22
+ }
23
+ else if (line.startsWith("data: ")) {
24
+ try {
25
+ onEvent(eventType, JSON.parse(line.slice(6)));
26
+ }
27
+ catch {
28
+ // skip malformed lines
29
+ }
30
+ eventType = "message";
31
+ }
32
+ }
33
+ }
34
+ }
35
+ finally {
36
+ reader.releaseLock();
37
+ }
38
+ }
39
+ const tool = {
40
+ name: "image_to_video_generation",
41
+ description: "Generate a video from a prompt and an image in the workspace.",
42
+ parameters: {
43
+ type: "object",
44
+ properties: {
45
+ prompt: { type: "string", description: "The video prompt" },
46
+ image: {
47
+ type: "string",
48
+ description: "Path of the image in the workspace to animate",
49
+ },
50
+ duration: {
51
+ type: "number",
52
+ description: "Video duration in seconds (default 5)",
53
+ },
54
+ mode: {
55
+ type: "string",
56
+ enum: ["distilled", "one-stage", "two-stage"],
57
+ description: "Generation mode: distilled (default), one-stage, or two-stage",
58
+ },
59
+ },
60
+ required: ["prompt", "image"],
61
+ },
62
+ run: async (args, ctx) => {
63
+ if (!isValidProjectId(ctx.projectId))
64
+ return "Invalid project ID.";
65
+ const prompt = typeof args.prompt === "string" ? args.prompt.trim() : "";
66
+ if (!prompt)
67
+ return "image_to_video_generation requires a prompt.";
68
+ const image = typeof args.image === "string" ? args.image.trim() : "";
69
+ if (!image)
70
+ return "image_to_video_generation requires an image from the workspace.";
71
+ const abs = resolveWorkspacePath(ctx.projectId, image);
72
+ if (!abs || !existsSync(abs)) {
73
+ return `Image not found: ${image}`;
74
+ }
75
+ // Classify the resolved real path (resolves symlinks) so a symlink named
76
+ // like an image but pointing at another file is not treated as an image.
77
+ let realAbs;
78
+ try {
79
+ realAbs = realpathSync(abs);
80
+ }
81
+ catch {
82
+ return `Image not found: ${image}`;
83
+ }
84
+ if (classifyFile(realAbs) !== "image") {
85
+ return `Not an image: ${image}`;
86
+ }
87
+ if (!ctx.backendPort)
88
+ return "Video backend not available.";
89
+ const duration = typeof args.duration === "number" && args.duration > 0
90
+ ? args.duration
91
+ : 5;
92
+ const frames = Math.round(duration * 24 + 1);
93
+ // Copy the image into the project's agent-upload dir so the video endpoint
94
+ // can resolve it by bare filename.
95
+ const uploadProjectDir = join(AGENT_UPLOAD_DIR, ctx.projectId);
96
+ if (!existsSync(uploadProjectDir)) {
97
+ mkdirSync(uploadProjectDir, { recursive: true });
98
+ }
99
+ const origName = image.split("/").pop() || "image.png";
100
+ const imageName = `agent-${Date.now()}-${origName.replace(/[^a-zA-Z0-9._-]/g, "_")}`;
101
+ writeFileSync(join(uploadProjectDir, imageName), readFileSync(realAbs));
102
+ ctx.emit?.("notice", { text: `Generating video from "${image}"...` });
103
+ try {
104
+ const body = {
105
+ prompt,
106
+ imagePath: imageName,
107
+ projectId: ctx.projectId,
108
+ outputDir: workspaceDir(ctx.projectId),
109
+ width: 448,
110
+ height: 448,
111
+ frames,
112
+ frameRate: 24,
113
+ };
114
+ if (typeof args.mode === "string" &&
115
+ ["distilled", "one-stage", "two-stage"].includes(args.mode)) {
116
+ body.mode = args.mode;
117
+ }
118
+ const res = await fetch(`http://localhost:${ctx.backendPort}/api/render/image-to-video`, {
119
+ method: "POST",
120
+ headers: { "Content-Type": "application/json" },
121
+ body: JSON.stringify(body),
122
+ signal: ctx.signal,
123
+ });
124
+ if (!res.ok) {
125
+ return `Video generation failed: ${await res.text()}`;
126
+ }
127
+ let resultPath = null;
128
+ let resultFilename = null;
129
+ let error = null;
130
+ await readSSE(res, (event, data) => {
131
+ switch (event) {
132
+ case "log":
133
+ ctx.emit?.("notice", { text: data.text });
134
+ break;
135
+ case "complete":
136
+ resultPath = data.path;
137
+ resultFilename = data.filename;
138
+ break;
139
+ case "error":
140
+ error = data.error || "Video generation failed";
141
+ break;
142
+ }
143
+ });
144
+ if (error)
145
+ return error;
146
+ if (!resultPath)
147
+ return "Video generation did not produce a result.";
148
+ const relPath = resultFilename ||
149
+ resultPath.split("/").pop() ||
150
+ "video.mp4";
151
+ const url = `/api/agent/file/preview?projectId=${encodeURIComponent(ctx.projectId)}&path=${encodeURIComponent(relPath)}`;
152
+ ctx.emit?.("video", { url });
153
+ return `Generated video from "${image}": ${resultPath}`;
154
+ }
155
+ catch (e) {
156
+ return `Video generation error: ${String(e)}`;
157
+ }
158
+ },
159
+ };
160
+ export default tool;
@@ -0,0 +1,7 @@
1
+ import type { AgentTool, ToolRunContext } from "./types";
2
+ export type { AgentTool, ToolRunContext } from "./types";
3
+ export declare const TOOLS: AgentTool[];
4
+ /** Build the OpenAI `tools` array from the tool objects. */
5
+ export declare function toolDefinitions(tools: AgentTool[]): any[];
6
+ /** Dispatch a tool call to the matching tool's `run` function. */
7
+ export declare function runTool(tools: AgentTool[], name: string, args: string, ctx: ToolRunContext): Promise<string>;
@@ -0,0 +1,59 @@
1
+ import getTimeTool from "./get-time";
2
+ import listFilesTool from "./list-files";
3
+ import readFileTool from "./read-file";
4
+ import writeFileTool from "./write-file";
5
+ import updateFileTool from "./update-file";
6
+ import removeFileTool from "./remove-file";
7
+ import renameFileTool from "./rename-file";
8
+ import grepFilesTool from "./grep-files";
9
+ import showImageTool from "./show-image";
10
+ import editImageTool from "./edit-image";
11
+ import imageToVideoGenerationTool from "./image-to-video-generation";
12
+ import textToVideoGenerationTool from "./text-to-video-generation";
13
+ import stitchVideosTool from "./stitch-videos";
14
+ export const TOOLS = [
15
+ getTimeTool,
16
+ listFilesTool,
17
+ readFileTool,
18
+ writeFileTool,
19
+ updateFileTool,
20
+ removeFileTool,
21
+ renameFileTool,
22
+ grepFilesTool,
23
+ showImageTool,
24
+ editImageTool,
25
+ imageToVideoGenerationTool,
26
+ textToVideoGenerationTool,
27
+ stitchVideosTool,
28
+ ];
29
+ /** Build the OpenAI `tools` array from the tool objects. */
30
+ export function toolDefinitions(tools) {
31
+ return tools.map((t) => ({
32
+ type: "function",
33
+ function: {
34
+ name: t.name,
35
+ description: t.description,
36
+ parameters: t.parameters,
37
+ },
38
+ }));
39
+ }
40
+ /** Dispatch a tool call to the matching tool's `run` function. */
41
+ export async function runTool(tools, name, args, ctx) {
42
+ const tool = tools.find((t) => t.name === name);
43
+ if (!tool)
44
+ return `Unknown tool: ${name}`;
45
+ let parsed = {};
46
+ try {
47
+ parsed = args ? JSON.parse(args) : {};
48
+ }
49
+ catch {
50
+ // leave empty on malformed args
51
+ }
52
+ try {
53
+ const out = await tool.run(parsed, ctx);
54
+ return typeof out === "string" ? out : String(out);
55
+ }
56
+ catch (e) {
57
+ return `Tool error: ${String(e)}`;
58
+ }
59
+ }
@@ -0,0 +1,3 @@
1
+ import type { AgentTool } from "./types";
2
+ declare const tool: AgentTool;
3
+ export default tool;
@@ -0,0 +1,17 @@
1
+ import { existsSync } from "node:fs";
2
+ import { workspaceDir, walkFiles } from "../workspace";
3
+ const tool = {
4
+ name: "list_files",
5
+ description: "List all files in the agent's workspace (recursive).",
6
+ parameters: { type: "object", properties: {}, additionalProperties: false },
7
+ run: (_args, ctx) => {
8
+ const base = workspaceDir(ctx.projectId);
9
+ if (!existsSync(base))
10
+ return "Workspace is empty.";
11
+ const files = walkFiles(base);
12
+ if (files.length === 0)
13
+ return "Workspace is empty.";
14
+ return JSON.stringify(files.map((f) => ({ path: f.path, kind: f.kind, size: f.size })));
15
+ },
16
+ };
17
+ export default tool;
@@ -0,0 +1,3 @@
1
+ import type { AgentTool } from "./types";
2
+ declare const tool: AgentTool;
3
+ export default tool;