@effectnode/media 0.3.0 → 0.5.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 (48) hide show
  1. package/dist/backend/movie-backend/agent/agent-backend.js +4 -4
  2. package/dist/backend/movie-backend/agent/prompt/story-writer.txt +137 -0
  3. package/dist/backend/movie-backend/agent/tools/edit-image.d.ts +1 -1
  4. package/dist/backend/movie-backend/agent/tools/edit-image.js +1 -1
  5. package/dist/backend/movie-backend/agent/tools/get-time.d.ts +1 -1
  6. package/dist/backend/movie-backend/agent/tools/grep-files.d.ts +1 -1
  7. package/dist/backend/movie-backend/agent/tools/grep-files.js +1 -1
  8. package/dist/backend/movie-backend/agent/tools/image-to-video-generation.d.ts +1 -1
  9. package/dist/backend/movie-backend/agent/tools/image-to-video-generation.js +1 -1
  10. package/dist/backend/movie-backend/agent/tools/index.d.ts +2 -2
  11. package/dist/backend/movie-backend/agent/tools/index.js +13 -13
  12. package/dist/backend/movie-backend/agent/tools/list-files.d.ts +1 -1
  13. package/dist/backend/movie-backend/agent/tools/list-files.js +1 -1
  14. package/dist/backend/movie-backend/agent/tools/read-file.d.ts +1 -1
  15. package/dist/backend/movie-backend/agent/tools/read-file.js +1 -1
  16. package/dist/backend/movie-backend/agent/tools/remove-file.d.ts +1 -1
  17. package/dist/backend/movie-backend/agent/tools/remove-file.js +1 -1
  18. package/dist/backend/movie-backend/agent/tools/rename-file.d.ts +1 -1
  19. package/dist/backend/movie-backend/agent/tools/rename-file.js +1 -1
  20. package/dist/backend/movie-backend/agent/tools/show-image.d.ts +1 -1
  21. package/dist/backend/movie-backend/agent/tools/show-image.js +1 -1
  22. package/dist/backend/movie-backend/agent/tools/stitch-videos.d.ts +1 -1
  23. package/dist/backend/movie-backend/agent/tools/stitch-videos.js +1 -1
  24. package/dist/backend/movie-backend/agent/tools/text-to-video-generation.d.ts +1 -1
  25. package/dist/backend/movie-backend/agent/tools/text-to-video-generation.js +1 -1
  26. package/dist/backend/movie-backend/agent/tools/update-file.d.ts +1 -1
  27. package/dist/backend/movie-backend/agent/tools/update-file.js +1 -1
  28. package/dist/backend/movie-backend/agent/tools/write-file.d.ts +1 -1
  29. package/dist/backend/movie-backend/agent/tools/write-file.js +1 -1
  30. package/dist/backend/movie-backend/core.js +4 -4
  31. package/dist/backend/movie-backend/generation-queue.js +102 -46
  32. package/dist/backend/movie-backend/render-media.js +1 -1
  33. package/frontend/index.html +8 -1
  34. package/frontend/public/lambobo.png +0 -0
  35. package/frontend/src/movie-app/MediaStudio.tsx +6 -2
  36. package/frontend/src/movie-app/SetupPage.tsx +98 -63
  37. package/frontend/src/movie-app/components/Aurora.tsx +13 -0
  38. package/frontend/src/movie-app/components/EditorTabs/GenerateVideoTab.tsx +5 -8
  39. package/frontend/src/movie-app/components/EditorTabs/MovieStudioTab.tsx +297 -189
  40. package/frontend/src/movie-app/components/EditorTabs/SetupAiModelTab.tsx +345 -0
  41. package/frontend/src/movie-app/components/ProjectEditorPage.tsx +40 -34
  42. package/frontend/src/movie-app/components/ProjectManager.tsx +80 -52
  43. package/frontend/src/movie-app/index.css +260 -19
  44. package/frontend/src/movie-app/stores/aiModelStore.ts +163 -0
  45. package/frontend/src/movie-app/stores/generationStore.ts +2 -2
  46. package/frontend/src/movie-app/stores/movieStudioStore.ts +45 -0
  47. package/package.json +2 -2
  48. package/frontend/src/movie-app/components/EditorTabs/ReferencesToVideoTab.tsx +0 -881
@@ -0,0 +1,345 @@
1
+ import { useEffect, type ReactNode } from "react";
2
+ import { useAiModelStore, type AiModelId } from "../../stores/aiModelStore";
3
+
4
+ const DownloadIcon = (
5
+ <svg
6
+ width="14"
7
+ height="14"
8
+ viewBox="0 0 24 24"
9
+ fill="none"
10
+ stroke="currentColor"
11
+ strokeWidth="2"
12
+ strokeLinecap="round"
13
+ strokeLinejoin="round"
14
+ >
15
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
16
+ <polyline points="7 10 12 15 17 10" />
17
+ <line x1="12" y1="15" x2="12" y2="3" />
18
+ </svg>
19
+ );
20
+
21
+ const InstallIcon = (
22
+ <svg
23
+ width="14"
24
+ height="14"
25
+ viewBox="0 0 24 24"
26
+ fill="none"
27
+ stroke="currentColor"
28
+ strokeWidth="2"
29
+ strokeLinecap="round"
30
+ strokeLinejoin="round"
31
+ >
32
+ <polyline points="16 18 22 12 16 6" />
33
+ <polyline points="8 6 2 12 8 18" />
34
+ </svg>
35
+ );
36
+
37
+ const CheckIcon = (
38
+ <svg
39
+ width="14"
40
+ height="14"
41
+ viewBox="0 0 24 24"
42
+ fill="none"
43
+ stroke="currentColor"
44
+ strokeWidth="2"
45
+ strokeLinecap="round"
46
+ strokeLinejoin="round"
47
+ >
48
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
49
+ <polyline points="22 4 12 14.01 9 11.01" />
50
+ </svg>
51
+ );
52
+
53
+ const AlertIcon = (
54
+ <svg
55
+ width="14"
56
+ height="14"
57
+ viewBox="0 0 24 24"
58
+ fill="none"
59
+ stroke="currentColor"
60
+ strokeWidth="2"
61
+ strokeLinecap="round"
62
+ strokeLinejoin="round"
63
+ >
64
+ <circle cx="12" cy="12" r="10" />
65
+ <line x1="12" y1="8" x2="12" y2="12" />
66
+ <line x1="12" y1="16" x2="12.01" y2="16" />
67
+ </svg>
68
+ );
69
+
70
+ const SpinnerIcon = (
71
+ <svg
72
+ className="animate-spin"
73
+ width="14"
74
+ height="14"
75
+ viewBox="0 0 24 24"
76
+ fill="none"
77
+ stroke="currentColor"
78
+ strokeWidth="2"
79
+ >
80
+ <circle cx="12" cy="12" r="10" strokeOpacity="0.25" />
81
+ <path d="M12 2a10 10 0 0 1 10 10" strokeOpacity="0.75" />
82
+ </svg>
83
+ );
84
+
85
+ const RefreshIcon = (
86
+ <svg
87
+ width="14"
88
+ height="14"
89
+ viewBox="0 0 24 24"
90
+ fill="none"
91
+ stroke="currentColor"
92
+ strokeWidth="2"
93
+ strokeLinecap="round"
94
+ strokeLinejoin="round"
95
+ >
96
+ <polyline points="23 4 23 10 17 10" />
97
+ <path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10" />
98
+ </svg>
99
+ );
100
+
101
+ const ChipIcon = (
102
+ <svg
103
+ width="18"
104
+ height="18"
105
+ viewBox="0 0 24 24"
106
+ fill="none"
107
+ stroke="currentColor"
108
+ strokeWidth="2"
109
+ strokeLinecap="round"
110
+ strokeLinejoin="round"
111
+ >
112
+ <rect x="4" y="4" width="16" height="16" rx="2" ry="2" />
113
+ <rect x="9" y="9" width="6" height="6" />
114
+ <line x1="9" y1="1" x2="9" y2="4" />
115
+ <line x1="15" y1="1" x2="15" y2="4" />
116
+ <line x1="9" y1="20" x2="9" y2="23" />
117
+ <line x1="15" y1="20" x2="15" y2="23" />
118
+ <line x1="20" y1="9" x2="23" y2="9" />
119
+ <line x1="20" y1="14" x2="23" y2="14" />
120
+ <line x1="1" y1="9" x2="4" y2="9" />
121
+ <line x1="1" y1="14" x2="4" y2="14" />
122
+ </svg>
123
+ );
124
+
125
+ function StatusBadge({
126
+ value,
127
+ okText = "Downloaded",
128
+ missingText = "Not downloaded",
129
+ }: {
130
+ value: boolean | null;
131
+ okText?: string;
132
+ missingText?: string;
133
+ }) {
134
+ if (value === null) {
135
+ return (
136
+ <span className="inline-flex items-center gap-1.5 text-xs text-ink-500">
137
+ {SpinnerIcon}
138
+ Checking…
139
+ </span>
140
+ );
141
+ }
142
+ if (value) {
143
+ return (
144
+ <span className="inline-flex items-center gap-1.5 text-xs font-medium text-emerald-600">
145
+ {CheckIcon}
146
+ {okText}
147
+ </span>
148
+ );
149
+ }
150
+ return (
151
+ <span className="inline-flex items-center gap-1.5 text-xs font-medium text-amber-600">
152
+ {AlertIcon}
153
+ {missingText}
154
+ </span>
155
+ );
156
+ }
157
+
158
+ function ModelRow({
159
+ name,
160
+ desc,
161
+ children,
162
+ }: {
163
+ name: string;
164
+ desc: string;
165
+ children?: ReactNode;
166
+ }) {
167
+ return (
168
+ <div className="flex items-center gap-3 rounded-2xl border border-ink-200 bg-white px-4 py-3">
169
+ <div className="min-w-0 flex-1">
170
+ <p className="truncate font-mono text-sm text-ink-900">{name}</p>
171
+ <p className="mt-0.5 text-xs text-ink-500">{desc}</p>
172
+ </div>
173
+ {children}
174
+ </div>
175
+ );
176
+ }
177
+
178
+ const DOWNLOAD_MODELS: {
179
+ id: AiModelId;
180
+ name: string;
181
+ desc: string;
182
+ }[] = [
183
+ {
184
+ id: "z-image",
185
+ name: "AbstractFramework/z-image-turbo-8bit",
186
+ desc: "Text-to-image",
187
+ },
188
+ {
189
+ id: "flux",
190
+ name: "AbstractFramework/flux.2-klein-4b-8bit",
191
+ desc: "Character, place & scene images",
192
+ },
193
+ {
194
+ id: "qwen",
195
+ name: "AbstractFramework/qwen-image-edit-2511-8bit",
196
+ desc: "Image editing",
197
+ },
198
+ ];
199
+
200
+ export default function SetupAiModelTab() {
201
+ const store = useAiModelStore();
202
+
203
+ useEffect(() => {
204
+ store.checkStatus();
205
+ const interval = setInterval(() => store.checkStatus(), 3000);
206
+ return () => clearInterval(interval);
207
+ // eslint-disable-next-line react-hooks/exhaustive-deps
208
+ }, []);
209
+
210
+ const busy = store.downloading !== null;
211
+
212
+ return (
213
+ <div className="flex flex-col gap-6">
214
+ {/* Header */}
215
+ <div className="flex items-center gap-2">
216
+ <span className="text-tiffany-600">{ChipIcon}</span>
217
+ <h2 className="text-base font-semibold text-ink-900">AI Models</h2>
218
+ <button
219
+ onClick={() => store.checkStatus()}
220
+ className="ml-auto flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-xl border border-ink-200 text-ink-600 hover:border-ink-300 hover:text-ink-900 transition-colors"
221
+ >
222
+ {RefreshIcon}
223
+ Refresh
224
+ </button>
225
+ </div>
226
+
227
+ {/* Engines */}
228
+ <section>
229
+ <h3 className="mb-2 text-sm font-semibold text-ink-900">Engines</h3>
230
+ <div className="space-y-2">
231
+ <ModelRow name="mlx-gen" desc="Image generation engine (z-image · flux · qwen)">
232
+ <StatusBadge
233
+ value={store.mlxgenInstalled}
234
+ okText="Installed"
235
+ missingText="Not installed"
236
+ />
237
+ <button
238
+ onClick={() => store.installTool("mlxgen")}
239
+ disabled={busy}
240
+ className="flex items-center gap-1.5 whitespace-nowrap rounded-xl border border-tiffany-500 px-3 py-1.5 text-xs font-medium text-tiffany-700 transition-colors hover:bg-tiffany-50 disabled:opacity-50"
241
+ >
242
+ {store.downloading === "mlxgen" ? SpinnerIcon : InstallIcon}
243
+ {store.downloading === "mlxgen" ? "Installing…" : "Install"}
244
+ </button>
245
+ </ModelRow>
246
+
247
+ <ModelRow name="mlx-vlm" desc="LLM server (gemma) for story planning">
248
+ <StatusBadge
249
+ value={store.mlxVlmInstalled}
250
+ okText="Installed"
251
+ missingText="Not installed"
252
+ />
253
+ <button
254
+ onClick={() => store.installTool("mlx-vlm")}
255
+ disabled={busy}
256
+ className="flex items-center gap-1.5 whitespace-nowrap rounded-xl border border-tiffany-500 px-3 py-1.5 text-xs font-medium text-tiffany-700 transition-colors hover:bg-tiffany-50 disabled:opacity-50"
257
+ >
258
+ {store.downloading === "mlx-vlm" ? SpinnerIcon : InstallIcon}
259
+ {store.downloading === "mlx-vlm" ? "Installing…" : "Install"}
260
+ </button>
261
+ </ModelRow>
262
+ </div>
263
+ </section>
264
+
265
+ {/* Models */}
266
+ <section>
267
+ <h3 className="mb-2 text-sm font-semibold text-ink-900">Models</h3>
268
+ <div className="space-y-2">
269
+ <ModelRow
270
+ name="mlx-community/gemma-4-e2b-it-4bit"
271
+ desc="Story planning & dialogue (LLM)"
272
+ >
273
+ <StatusBadge
274
+ value={store.mlxVlmInstalled}
275
+ okText="Ready"
276
+ missingText="Not set up"
277
+ />
278
+ <span className="text-xs italic text-ink-400">
279
+ Downloads on first server start
280
+ </span>
281
+ </ModelRow>
282
+
283
+ {DOWNLOAD_MODELS.map((m) => {
284
+ const downloaded =
285
+ m.id === "z-image"
286
+ ? store.zImageDownloaded
287
+ : m.id === "flux"
288
+ ? store.fluxDownloaded
289
+ : store.qwenDownloaded;
290
+ return (
291
+ <ModelRow key={m.id} name={m.name} desc={m.desc}>
292
+ <StatusBadge value={downloaded} />
293
+ <button
294
+ onClick={() => store.downloadModel(m.id)}
295
+ disabled={busy}
296
+ className="flex items-center gap-1.5 whitespace-nowrap rounded-xl bg-tiffany-500 px-3 py-1.5 text-xs font-medium text-ink-950 transition-colors hover:bg-tiffany-600 disabled:opacity-50"
297
+ >
298
+ {store.downloading === m.id ? SpinnerIcon : DownloadIcon}
299
+ {store.downloading === m.id ? "Downloading…" : "Download"}
300
+ </button>
301
+ </ModelRow>
302
+ );
303
+ })}
304
+
305
+ <ModelRow
306
+ name="dgrauet/ltx-2.3-mlx-q8"
307
+ desc="Video generation"
308
+ >
309
+ <span className="text-xs italic text-ink-400">
310
+ Downloads on first render
311
+ </span>
312
+ </ModelRow>
313
+
314
+ <ModelRow
315
+ name="Qwen/Qwen3-TTS-12Hz-1.7B-Base"
316
+ desc="Voice-over & speech"
317
+ >
318
+ <span className="text-xs italic text-ink-400">
319
+ Downloads on first use
320
+ </span>
321
+ </ModelRow>
322
+ </div>
323
+ </section>
324
+
325
+ {/* Error */}
326
+ {store.error && (
327
+ <div className="rounded-2xl border border-rose-200 bg-rose-50/70 px-4 py-3 text-xs text-rose-600">
328
+ {store.error}
329
+ </div>
330
+ )}
331
+
332
+ {/* Download logs */}
333
+ {store.logs.length > 0 && (
334
+ <div className="rounded-2xl border border-ink-200 bg-ink-50 p-4">
335
+ <p className="mb-2 text-[11px] font-semibold uppercase tracking-wider text-ink-500">
336
+ Output
337
+ </p>
338
+ <pre className="max-h-48 overflow-y-auto whitespace-pre-wrap font-mono text-xs leading-relaxed text-ink-700">
339
+ {store.logs.join("")}
340
+ </pre>
341
+ </div>
342
+ )}
343
+ </div>
344
+ );
345
+ }
@@ -12,11 +12,11 @@ import CharactersTab from "./EditorTabs/CharactersTab";
12
12
  import ExtractImageTab from "./EditorTabs/ExtractImageTab";
13
13
  import SceneVisualTab from "./EditorTabs/SceneVisualTab";
14
14
  import TextToImageTab from "./EditorTabs/TextToImageTab";
15
- import ReferencesToVideoTab from "./EditorTabs/ReferencesToVideoTab";
16
15
  import BatchVideoTab from "./EditorTabs/BatchVideoTab";
17
16
  import BatchImageToVideoTab from "./EditorTabs/BatchImageToVideoTab";
18
17
  import BatchVoiceVideoTab from "./EditorTabs/BatchVoiceVideoTab";
19
18
  import LlmServerTab from "./EditorTabs/LlmServerTab";
19
+ import SetupAiModelTab from "./EditorTabs/SetupAiModelTab";
20
20
 
21
21
  export default function ProjectEditorPage() {
22
22
  const { id } = useParams<{ id: string }>();
@@ -237,22 +237,6 @@ export default function ProjectEditorPage() {
237
237
  </svg>
238
238
  );
239
239
 
240
- const ReferencesToVideoIcon = (
241
- <svg
242
- width="18"
243
- height="18"
244
- viewBox="0 0 24 24"
245
- fill="none"
246
- stroke="currentColor"
247
- strokeWidth="2"
248
- strokeLinecap="round"
249
- strokeLinejoin="round"
250
- >
251
- <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
252
- <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
253
- </svg>
254
- );
255
-
256
240
  const BatchVideoIcon = (
257
241
  <svg
258
242
  width="18"
@@ -335,6 +319,30 @@ export default function ProjectEditorPage() {
335
319
  />
336
320
  );
337
321
 
322
+ const AiModelIcon = (
323
+ <svg
324
+ width="18"
325
+ height="18"
326
+ viewBox="0 0 24 24"
327
+ fill="none"
328
+ stroke="currentColor"
329
+ strokeWidth="2"
330
+ strokeLinecap="round"
331
+ strokeLinejoin="round"
332
+ >
333
+ <rect x="4" y="4" width="16" height="16" rx="2" ry="2" />
334
+ <rect x="9" y="9" width="6" height="6" />
335
+ <line x1="9" y1="1" x2="9" y2="4" />
336
+ <line x1="15" y1="1" x2="15" y2="4" />
337
+ <line x1="9" y1="20" x2="9" y2="23" />
338
+ <line x1="15" y1="20" x2="15" y2="23" />
339
+ <line x1="20" y1="9" x2="23" y2="9" />
340
+ <line x1="20" y1="14" x2="23" y2="14" />
341
+ <line x1="1" y1="9" x2="4" y2="9" />
342
+ <line x1="1" y1="14" x2="4" y2="14" />
343
+ </svg>
344
+ );
345
+
338
346
  // ========== Loading / Not Found ==========
339
347
 
340
348
  if (!project) {
@@ -417,7 +425,7 @@ export default function ProjectEditorPage() {
417
425
  }`}
418
426
  >
419
427
  {VideoIcon}
420
- Generate Avatar ideo
428
+ Generate Avatar Video
421
429
  </button>
422
430
  <button
423
431
  onClick={() => store.setActiveTab("extend")}
@@ -507,17 +515,6 @@ export default function ProjectEditorPage() {
507
515
  {TextToImageIcon}
508
516
  Text-to-Image
509
517
  </button>
510
- <button
511
- onClick={() => store.setActiveTab("referencesToVideo")}
512
- className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium rounded-2xl transition-all ${
513
- store.activeTab === "referencesToVideo"
514
- ? "bg-tiffany-500/10 text-tiffany-700 shadow-glow-sm"
515
- : "text-ink-600 hover:bg-ink-100 hover:text-ink-800"
516
- }`}
517
- >
518
- {ReferencesToVideoIcon}
519
- References to Video
520
- </button>
521
518
  <button
522
519
  onClick={() => store.setActiveTab("batchVideo")}
523
520
  className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium rounded-2xl transition-all ${
@@ -563,6 +560,17 @@ export default function ProjectEditorPage() {
563
560
  LLM Server
564
561
  <ServerStatusLight />
565
562
  </button>
563
+ <button
564
+ onClick={() => store.setActiveTab("aiModels")}
565
+ className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium rounded-2xl transition-all ${
566
+ store.activeTab === "aiModels"
567
+ ? "bg-tiffany-500/10 text-tiffany-700 shadow-glow-sm"
568
+ : "text-ink-600 hover:bg-ink-100 hover:text-ink-800"
569
+ }`}
570
+ >
571
+ {AiModelIcon}
572
+ Setup AI Models
573
+ </button>
566
574
  </div>
567
575
 
568
576
  {/* ========== MOVIE STUDIO PANEL ========== */}
@@ -607,11 +615,6 @@ export default function ProjectEditorPage() {
607
615
  <TextToImageTab projectId={id!} />
608
616
  )}
609
617
 
610
- {/* ========== REFERENCES TO VIDEO PANEL ========== */}
611
- {store.activeTab === "referencesToVideo" && (
612
- <ReferencesToVideoTab projectId={id!} />
613
- )}
614
-
615
618
  {/* ========== BATCH VIDEO PANEL ========== */}
616
619
  {store.activeTab === "batchVideo" && (
617
620
  <BatchVideoTab projectId={id!} />
@@ -629,6 +632,9 @@ export default function ProjectEditorPage() {
629
632
 
630
633
  {/* ========== LLM SERVER PANEL ========== */}
631
634
  {store.activeTab === "llmServer" && <LlmServerTab />}
635
+
636
+ {/* ========== SETUP AI MODELS PANEL ========== */}
637
+ {store.activeTab === "aiModels" && <SetupAiModelTab />}
632
638
  </div>
633
639
  </div>
634
640
  </div>