@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,163 @@
1
+ import { create } from "zustand";
2
+
3
+ const API_BASE = `http://localhost:${(window as any).PORT}`;
4
+
5
+ /** Models that have a dedicated download endpoint in the backend. */
6
+ export type AiModelId = "z-image" | "flux" | "qwen";
7
+
8
+ /** Tools whose "install" step must run before their models can be downloaded. */
9
+ export type AiToolId = "mlxgen" | "mlx-vlm";
10
+
11
+ interface AiModelStore {
12
+ // Tool installation status
13
+ mlxgenInstalled: boolean | null;
14
+ mlxVlmInstalled: boolean | null;
15
+ // Model download status
16
+ zImageDownloaded: boolean | null;
17
+ fluxDownloaded: boolean | null;
18
+ qwenDownloaded: boolean | null;
19
+ // In-flight install/download (a model id or tool id)
20
+ downloading: string | null;
21
+ logs: string[];
22
+ error: string | null;
23
+
24
+ checkStatus: () => Promise<void>;
25
+ installTool: (tool: AiToolId) => Promise<void>;
26
+ downloadModel: (id: AiModelId) => Promise<void>;
27
+ clearLogs: () => void;
28
+ }
29
+
30
+ /** Read a server-sent-events response, invoking `onEvent` per parsed event. */
31
+ async function readSSE(
32
+ response: Response,
33
+ onEvent: (event: string, data: any) => void,
34
+ ): Promise<void> {
35
+ const reader = response.body?.getReader();
36
+ if (!reader) return;
37
+ const decoder = new TextDecoder();
38
+ let buffer = "";
39
+ try {
40
+ while (true) {
41
+ const { done, value } = await reader.read();
42
+ if (done) break;
43
+ buffer += decoder.decode(value, { stream: true });
44
+ const lines = buffer.split("\n");
45
+ buffer = lines.pop() || "";
46
+ let eventType = "message";
47
+ for (const line of lines) {
48
+ if (line.startsWith("event: ")) {
49
+ eventType = line.slice(7).trim();
50
+ } else if (line.startsWith("data: ")) {
51
+ try {
52
+ onEvent(eventType, JSON.parse(line.slice(6)));
53
+ } catch {
54
+ // skip malformed lines
55
+ }
56
+ eventType = "message";
57
+ }
58
+ }
59
+ }
60
+ } finally {
61
+ reader.releaseLock();
62
+ }
63
+ }
64
+
65
+ const DOWNLOAD_ENDPOINTS: Record<AiModelId, string> = {
66
+ "z-image": "/api/mlxgen/download-z-model",
67
+ flux: "/api/mlxgen/download-flux-model",
68
+ qwen: "/api/mlxgen/download-model",
69
+ };
70
+
71
+ const TOOL_ENDPOINTS: Record<AiToolId, string> = {
72
+ mlxgen: "/api/mlxgen/install",
73
+ "mlx-vlm": "/api/agent/install",
74
+ };
75
+
76
+ export const useAiModelStore = create<AiModelStore>((set, get) => ({
77
+ mlxgenInstalled: null,
78
+ mlxVlmInstalled: null,
79
+ zImageDownloaded: null,
80
+ fluxDownloaded: null,
81
+ qwenDownloaded: null,
82
+ downloading: null,
83
+ logs: [],
84
+ error: null,
85
+
86
+ checkStatus: async () => {
87
+ try {
88
+ const [mlxgen, agent] = await Promise.all([
89
+ fetch(`${API_BASE}/api/mlxgen/status`).then((r) =>
90
+ r.ok ? r.json() : null,
91
+ ),
92
+ fetch(`${API_BASE}/api/agent/status`).then((r) =>
93
+ r.ok ? r.json() : null,
94
+ ),
95
+ ]);
96
+ set({
97
+ mlxgenInstalled: mlxgen ? Boolean(mlxgen.installed) : null,
98
+ qwenDownloaded: mlxgen ? Boolean(mlxgen.modelDownloaded) : null,
99
+ zImageDownloaded: mlxgen ? Boolean(mlxgen.zModelDownloaded) : null,
100
+ fluxDownloaded: mlxgen ? Boolean(mlxgen.fluxModelDownloaded) : null,
101
+ mlxVlmInstalled: agent ? Boolean(agent.installed) : null,
102
+ });
103
+ } catch {
104
+ // Leave status unknown (null) if the checks fail.
105
+ }
106
+ },
107
+
108
+ installTool: async (tool) => {
109
+ if (get().downloading) return;
110
+ set({ downloading: tool, logs: [], error: null });
111
+ try {
112
+ const res = await fetch(`${API_BASE}${TOOL_ENDPOINTS[tool]}`, {
113
+ method: "POST",
114
+ });
115
+ if (!res.ok) {
116
+ set({ downloading: null, error: await res.text() });
117
+ return;
118
+ }
119
+ await readSSE(res, (event, data) => {
120
+ if (event === "log") {
121
+ set((s) => ({ logs: [...s.logs, data.text ?? ""] }));
122
+ } else if (event === "complete") {
123
+ set({ downloading: null });
124
+ } else if (event === "error") {
125
+ set({ downloading: null, error: data.error ?? "Install failed" });
126
+ }
127
+ });
128
+ } catch (e) {
129
+ set({ downloading: null, error: String(e) });
130
+ } finally {
131
+ get().checkStatus();
132
+ }
133
+ },
134
+
135
+ downloadModel: async (id) => {
136
+ if (get().downloading) return;
137
+ set({ downloading: id, logs: [], error: null });
138
+ try {
139
+ const res = await fetch(`${API_BASE}${DOWNLOAD_ENDPOINTS[id]}`, {
140
+ method: "POST",
141
+ });
142
+ if (!res.ok) {
143
+ set({ downloading: null, error: await res.text() });
144
+ return;
145
+ }
146
+ await readSSE(res, (event, data) => {
147
+ if (event === "log") {
148
+ set((s) => ({ logs: [...s.logs, data.text ?? ""] }));
149
+ } else if (event === "complete") {
150
+ set({ downloading: null });
151
+ } else if (event === "error") {
152
+ set({ downloading: null, error: data.error ?? "Download failed" });
153
+ }
154
+ });
155
+ } catch (e) {
156
+ set({ downloading: null, error: String(e) });
157
+ } finally {
158
+ get().checkStatus();
159
+ }
160
+ },
161
+
162
+ clearLogs: () => set({ logs: [], error: null }),
163
+ }));
@@ -22,11 +22,11 @@ export type GenerationTab =
22
22
  | "extract"
23
23
  | "sceneVisual"
24
24
  | "textToImage"
25
- | "referencesToVideo"
26
25
  | "batchVideo"
27
26
  | "batchImageToVideo"
28
27
  | "batchVoice"
29
- | "llmServer";
28
+ | "llmServer"
29
+ | "aiModels";
30
30
  export type AspectRatio = "1:1" | "16:9" | "9:16" | "4:3" | "3:4";
31
31
  export type Resolution =
32
32
  | "320p"
@@ -183,6 +183,9 @@ interface MovieStudioStore {
183
183
  regenerateVideo: (projectId: string, slug: string) => Promise<void>;
184
184
  renderSceneImages: (projectId: string) => Promise<void>;
185
185
  regenerateSceneImage: (projectId: string, slug: string) => Promise<void>;
186
+ updateCharacter: (slug: string, patch: Partial<MovieCharacter>) => void;
187
+ updatePlace: (slug: string, patch: Partial<MoviePlace>) => void;
188
+ updateScene: (slug: string, patch: Partial<MovieScene>) => void;
186
189
  applyQueueTask: (task: QueueTask) => void;
187
190
  primeAppliedQueue: (tasks: QueueTask[]) => void;
188
191
  stop: () => void;
@@ -410,6 +413,48 @@ export const useMovieStudioStore = create<MovieStudioStore>((set, get) => ({
410
413
  }
411
414
  },
412
415
 
416
+ updateCharacter: (slug, patch) => {
417
+ const result = get().result;
418
+ if (!result) return;
419
+ set({
420
+ result: {
421
+ ...result,
422
+ characters: result.characters.map((c) =>
423
+ c.slug === slug ? { ...c, ...patch } : c,
424
+ ),
425
+ },
426
+ });
427
+ persistMovieStudioState();
428
+ },
429
+
430
+ updatePlace: (slug, patch) => {
431
+ const result = get().result;
432
+ if (!result) return;
433
+ set({
434
+ result: {
435
+ ...result,
436
+ places: result.places.map((p) =>
437
+ p.slug === slug ? { ...p, ...patch } : p,
438
+ ),
439
+ },
440
+ });
441
+ persistMovieStudioState();
442
+ },
443
+
444
+ updateScene: (slug, patch) => {
445
+ const result = get().result;
446
+ if (!result) return;
447
+ set({
448
+ result: {
449
+ ...result,
450
+ scenes: result.scenes.map((s) =>
451
+ s.slug === slug ? { ...s, ...patch } : s,
452
+ ),
453
+ },
454
+ });
455
+ persistMovieStudioState();
456
+ },
457
+
413
458
  // Reconcile the movie studio store with the latest queue task state.
414
459
  applyQueueTask: (task) => {
415
460
  const isActive = task.status === "pending" || task.status === "running";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effectnode/media",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Start a full-stack media app: Vite + React + TypeScript frontend, Express backend with REST + WebSocket API",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -18,7 +18,7 @@
18
18
  ],
19
19
  "scripts": {
20
20
  "deploy": "npm run build; npm version minor; npm publish --access public",
21
- "build": "tsc",
21
+ "build": "tsc && node -e \"const fs=require('node:fs');fs.cpSync('src/backend/movie-backend/agent/prompt','dist/backend/movie-backend/agent/prompt',{recursive:true})\"",
22
22
  "dev": "concurrently -k -n backend,frontend -c blue,green \"bun run dev:backend\" \"bun run dev:frontend\"",
23
23
  "dev:backend": "nodemon",
24
24
  "dev:frontend": "vite",