@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,170 @@
1
+ import { create } from "zustand";
2
+
3
+ const API_BASE = `http://localhost:${(window as any).PORT}`;
4
+
5
+ interface VoiceChatStore {
6
+ // Reference voice (voice clone source)
7
+ refAudioPath: string | null;
8
+ refAudioUrl: string | null;
9
+ refAudioFilename: string | null;
10
+
11
+ // Message text
12
+ text: string;
13
+
14
+ // Generation state
15
+ generating: boolean;
16
+ resultUrl: string | null;
17
+ error: string | null;
18
+ logs: string[];
19
+
20
+ // Speech-to-text state
21
+ listening: boolean;
22
+
23
+ setText: (v: string) => void;
24
+ setListening: (v: boolean) => void;
25
+ uploadRefAudio: (
26
+ base64: string,
27
+ filename: string,
28
+ projectId: string,
29
+ ) => Promise<string | null>;
30
+ generate: (projectId: string) => Promise<void>;
31
+ }
32
+
33
+ async function readSSEStream(
34
+ response: Response,
35
+ onEvent: (event: string, data: any) => void,
36
+ ): Promise<void> {
37
+ const reader = response.body?.getReader();
38
+ if (!reader) return;
39
+
40
+ const decoder = new TextDecoder();
41
+ let buffer = "";
42
+
43
+ try {
44
+ while (true) {
45
+ const { done, value } = await reader.read();
46
+ if (done) break;
47
+
48
+ buffer += decoder.decode(value, { stream: true });
49
+ const lines = buffer.split("\n");
50
+ buffer = lines.pop() || "";
51
+
52
+ let eventType = "message";
53
+ for (const line of lines) {
54
+ if (line.startsWith("event: ")) {
55
+ eventType = line.slice(7).trim();
56
+ } else if (line.startsWith("data: ")) {
57
+ try {
58
+ onEvent(eventType, JSON.parse(line.slice(6)));
59
+ } catch {
60
+ // skip malformed lines
61
+ }
62
+ eventType = "message";
63
+ }
64
+ }
65
+ }
66
+ } finally {
67
+ reader.releaseLock();
68
+ }
69
+ }
70
+
71
+ export const useVoiceChatStore = create<VoiceChatStore>((set, get) => ({
72
+ refAudioPath: null,
73
+ refAudioUrl: null,
74
+ refAudioFilename: null,
75
+
76
+ text: "",
77
+
78
+ generating: false,
79
+ resultUrl: null,
80
+ error: null,
81
+ logs: [],
82
+
83
+ listening: false,
84
+
85
+ setText: (text) => set({ text }),
86
+ setListening: (listening) => set({ listening }),
87
+
88
+ uploadRefAudio: async (base64, filename, projectId) => {
89
+ try {
90
+ const res = await fetch(`${API_BASE}/api/upload/audio`, {
91
+ method: "POST",
92
+ headers: { "Content-Type": "application/json" },
93
+ body: JSON.stringify({
94
+ audio: base64,
95
+ filename: filename || `voice-${Date.now()}.mp3`,
96
+ projectId,
97
+ }),
98
+ });
99
+
100
+ if (!res.ok) {
101
+ set({ error: await res.text() });
102
+ return null;
103
+ }
104
+
105
+ const data = await res.json();
106
+ set({
107
+ refAudioPath: data.path,
108
+ refAudioUrl: `http://localhost:${(window as any).PORT}/api/files?path=${encodeURIComponent(data.path)}`,
109
+ refAudioFilename: data.filename,
110
+ });
111
+ return data.path as string;
112
+ } catch (e) {
113
+ set({ error: String(e) });
114
+ return null;
115
+ }
116
+ },
117
+
118
+ generate: async (projectId) => {
119
+ if (get().generating) return;
120
+
121
+ const { text, refAudioFilename } = get();
122
+ if (!text.trim()) return;
123
+ if (!refAudioFilename) {
124
+ set({ error: "Upload a reference voice first." });
125
+ return;
126
+ }
127
+
128
+ set({ generating: true, error: null, resultUrl: null, logs: [] });
129
+
130
+ try {
131
+ const res = await fetch(`${API_BASE}/api/render/voice-chat`, {
132
+ method: "POST",
133
+ headers: { "Content-Type": "application/json" },
134
+ body: JSON.stringify({
135
+ text: text.trim(),
136
+ refAudioPath: refAudioFilename,
137
+ projectId,
138
+ }),
139
+ });
140
+
141
+ if (!res.ok) {
142
+ const err = await res.text();
143
+ set({ generating: false, error: err });
144
+ return;
145
+ }
146
+
147
+ await readSSEStream(res, (event, data) => {
148
+ switch (event) {
149
+ case "log":
150
+ set((s) => ({ logs: [...s.logs, data.text as string] }));
151
+ break;
152
+ case "complete":
153
+ set({
154
+ generating: false,
155
+ resultUrl: `http://localhost:${(window as any).PORT}/api/files?path=${encodeURIComponent(data.path)}`,
156
+ });
157
+ break;
158
+ case "error":
159
+ set({
160
+ generating: false,
161
+ error: data.error || "Voice generation failed",
162
+ });
163
+ break;
164
+ }
165
+ });
166
+ } catch (e) {
167
+ set({ generating: false, error: String(e) });
168
+ }
169
+ },
170
+ }));
@@ -0,0 +1,158 @@
1
+ import { create } from "zustand";
2
+
3
+ const API_BASE = `http://localhost:${(window as any).PORT}`;
4
+
5
+ export type FileKind = "image" | "video" | "text" | "other";
6
+
7
+ export interface WorkspaceFile {
8
+ path: string;
9
+ name: string;
10
+ ext: string;
11
+ size: number;
12
+ mtime: number;
13
+ kind: FileKind;
14
+ }
15
+
16
+ export function workspacePreviewUrl(
17
+ projectId: string,
18
+ path: string,
19
+ ): string {
20
+ return `${API_BASE}/api/agent/file/preview?projectId=${encodeURIComponent(
21
+ projectId,
22
+ )}&path=${encodeURIComponent(path)}`;
23
+ }
24
+
25
+ interface WorkspaceStore {
26
+ files: WorkspaceFile[];
27
+ loading: boolean;
28
+ error: string | null;
29
+ fetchFiles: (projectId: string) => Promise<void>;
30
+ uploadFile: (
31
+ projectId: string,
32
+ dataUrl: string,
33
+ filename: string,
34
+ ) => Promise<boolean>;
35
+ removeFile: (projectId: string, path: string) => Promise<void>;
36
+ renameFile: (
37
+ projectId: string,
38
+ path: string,
39
+ newName: string,
40
+ ) => Promise<void>;
41
+ readFileContent: (projectId: string, path: string) => Promise<string | null>;
42
+ writeFileContent: (
43
+ projectId: string,
44
+ path: string,
45
+ content: string,
46
+ ) => Promise<boolean>;
47
+ openWorkspace: (projectId: string) => Promise<void>;
48
+ }
49
+
50
+ export const useWorkspaceStore = create<WorkspaceStore>((set, get) => ({
51
+ files: [],
52
+ loading: false,
53
+ error: null,
54
+
55
+ fetchFiles: async (projectId) => {
56
+ set({ loading: true, error: null });
57
+ try {
58
+ const res = await fetch(
59
+ `${API_BASE}/api/agent/files?projectId=${encodeURIComponent(projectId)}`,
60
+ );
61
+ if (!res.ok) throw new Error(await res.text());
62
+ const data = await res.json();
63
+ set({ files: data.files || [], loading: false });
64
+ } catch (e) {
65
+ set({ loading: false, error: String(e) });
66
+ }
67
+ },
68
+
69
+ uploadFile: async (projectId, dataUrl, filename) => {
70
+ // Strip any directory component — only the basename is ever sent.
71
+ const safeName = filename.split(/[/\\]/).pop() || "upload";
72
+ try {
73
+ const res = await fetch(`${API_BASE}/api/agent/upload`, {
74
+ method: "POST",
75
+ headers: { "Content-Type": "application/json" },
76
+ body: JSON.stringify({ image: dataUrl, filename: safeName, projectId }),
77
+ });
78
+ if (!res.ok) throw new Error(await res.text());
79
+ await get().fetchFiles(projectId);
80
+ return true;
81
+ } catch (e) {
82
+ set({ error: String(e) });
83
+ return false;
84
+ }
85
+ },
86
+
87
+ removeFile: async (projectId, path) => {
88
+ try {
89
+ const res = await fetch(`${API_BASE}/api/agent/delete`, {
90
+ method: "POST",
91
+ headers: { "Content-Type": "application/json" },
92
+ body: JSON.stringify({ projectId, path }),
93
+ });
94
+ if (!res.ok) throw new Error(await res.text());
95
+ await get().fetchFiles(projectId);
96
+ } catch (e) {
97
+ set({ error: String(e) });
98
+ }
99
+ },
100
+
101
+ renameFile: async (projectId, path, newName) => {
102
+ try {
103
+ const res = await fetch(`${API_BASE}/api/agent/rename`, {
104
+ method: "POST",
105
+ headers: { "Content-Type": "application/json" },
106
+ body: JSON.stringify({ projectId, path, newName }),
107
+ });
108
+ if (!res.ok) throw new Error(await res.text());
109
+ await get().fetchFiles(projectId);
110
+ } catch (e) {
111
+ set({ error: String(e) });
112
+ }
113
+ },
114
+
115
+ readFileContent: async (projectId, path) => {
116
+ try {
117
+ const res = await fetch(
118
+ `${API_BASE}/api/agent/file/content?projectId=${encodeURIComponent(
119
+ projectId,
120
+ )}&path=${encodeURIComponent(path)}`,
121
+ );
122
+ if (!res.ok) throw new Error(await res.text());
123
+ const data = await res.json();
124
+ return data.content ?? "";
125
+ } catch (e) {
126
+ set({ error: String(e) });
127
+ return null;
128
+ }
129
+ },
130
+
131
+ writeFileContent: async (projectId, path, content) => {
132
+ try {
133
+ const res = await fetch(`${API_BASE}/api/agent/file/content`, {
134
+ method: "POST",
135
+ headers: { "Content-Type": "application/json" },
136
+ body: JSON.stringify({ projectId, path, content }),
137
+ });
138
+ if (!res.ok) throw new Error(await res.text());
139
+ await get().fetchFiles(projectId);
140
+ return true;
141
+ } catch (e) {
142
+ set({ error: String(e) });
143
+ return false;
144
+ }
145
+ },
146
+
147
+ openWorkspace: async (projectId) => {
148
+ try {
149
+ await fetch(`${API_BASE}/api/agent/open-workspace`, {
150
+ method: "POST",
151
+ headers: { "Content-Type": "application/json" },
152
+ body: JSON.stringify({ projectId }),
153
+ });
154
+ } catch (e) {
155
+ set({ error: String(e) });
156
+ }
157
+ },
158
+ }));
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "isolatedModules": true,
10
+ "moduleDetection": "force",
11
+ "noEmit": true,
12
+ "jsx": "react-jsx",
13
+ "strict": true,
14
+ "noUnusedLocals": true,
15
+ "noUnusedParameters": true,
16
+ "noFallthroughCasesInSwitch": true
17
+ },
18
+ "include": ["src"]
19
+ }
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@effectnode/media",
3
+ "version": "0.1.0",
4
+ "description": "Start a full-stack media app: Vite + React + TypeScript frontend, Express backend with REST + WebSocket API",
5
+ "license": "MIT",
6
+ "author": "",
7
+ "type": "module",
8
+ "repository": {
9
+ "url": "https://github.com/wonglok/effectnode-media"
10
+ },
11
+ "bin": {
12
+ "effectnode-media": "./bin/effectnode-media"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "bin",
17
+ "frontend"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "dev": "concurrently -k -n backend,frontend -c blue,green \"bun run dev:backend\" \"bun run dev:frontend\"",
22
+ "dev:backend": "nodemon",
23
+ "dev:frontend": "vite",
24
+ "start": "node bin/effectnode-media"
25
+ },
26
+ "dependencies": {
27
+ "@ffmpeg/core": "^0.12.10",
28
+ "@ffmpeg/ffmpeg": "^0.12.15",
29
+ "@ffmpeg/util": "^0.12.2",
30
+ "@vitejs/plugin-react": "^4.3.4",
31
+ "chalk": "^5.4.1",
32
+ "commander": "^13.1.0",
33
+ "cors": "^2.8.5",
34
+ "express": "^5.1.0",
35
+ "localforage": "^1.10.0",
36
+ "mustache": "^4.2.0",
37
+ "open": "^10.1.0",
38
+ "openai": "^7.5.0",
39
+ "papaparse": "^5.6.0",
40
+ "react": "^19.0.0",
41
+ "react-dom": "^19.0.0",
42
+ "react-markdown": "^10.1.0",
43
+ "react-router": "^8.3.0",
44
+ "react-router-dom": "^7.18.2",
45
+ "remark-gfm": "^4.0.1",
46
+ "sharp": "^0.35.3",
47
+ "vite": "^6.0.0",
48
+ "ws": "^8.18.0",
49
+ "zustand": "^5.0.15"
50
+ },
51
+ "devDependencies": {
52
+ "@tailwindcss/vite": "^4.3.3",
53
+ "@types/cors": "^2.8.17",
54
+ "@types/express": "^5.0.0",
55
+ "@types/node": "^22.13.0",
56
+ "@types/react": "^19.0.0",
57
+ "@types/react-dom": "^19.0.0",
58
+ "@types/ws": "^8.5.14",
59
+ "concurrently": "^10.0.5",
60
+ "nodemon": "^3.1.0",
61
+ "tailwindcss": "^4.3.3",
62
+ "tsx": "^4.19.0",
63
+ "typescript": "^5.7.0"
64
+ }
65
+ }