@effectnode/media 0.8.0 → 0.10.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.
- package/dist/backend/movie-backend/agent/prompt/script.md +213 -0
- package/dist/backend/movie-backend/core.js +33 -0
- package/dist/backend/movie-backend/generation-queue.d.ts +1 -1
- package/dist/backend/movie-backend/generation-queue.js +82 -6
- package/dist/backend/movie-backend/render-media.d.ts +89 -1
- package/dist/backend/movie-backend/render-media.js +772 -476
- package/frontend/src/movie-app/components/EditorTabs/AdvancedVoiceCloneTab.tsx +366 -0
- package/frontend/src/movie-app/components/EditorTabs/AudioToVideoTab.tsx +406 -0
- package/frontend/src/movie-app/components/EditorTabs/FastImageEditTab.tsx +67 -51
- package/frontend/src/movie-app/components/EditorTabs/GenerateVideoTab.tsx +155 -1
- package/frontend/src/movie-app/components/EditorTabs/MovieStudioTab.tsx +33 -13
- package/frontend/src/movie-app/components/EditorTabs/SetupAiModelTab.tsx +26 -5
- package/frontend/src/movie-app/components/EditorTabs/UpscaleTab.tsx +342 -0
- package/frontend/src/movie-app/components/EditorTabs/VoiceCloneTab.tsx +365 -0
- package/frontend/src/movie-app/components/ProjectEditorPage.tsx +131 -126
- package/frontend/src/movie-app/stores/advancedVoiceCloneStore.ts +207 -0
- package/frontend/src/movie-app/stores/aiModelStore.ts +25 -2
- package/frontend/src/movie-app/stores/audioToVideoStore.ts +274 -0
- package/frontend/src/movie-app/stores/generationStore.ts +250 -345
- package/frontend/src/movie-app/stores/movieStudioStore.ts +10 -3
- package/frontend/src/movie-app/stores/projectStore.ts +7 -3
- package/frontend/src/movie-app/stores/queueStore.ts +48 -1
- package/frontend/src/movie-app/stores/upscaleStore.ts +118 -0
- package/frontend/src/movie-app/stores/voiceCloneStore.ts +227 -0
- package/package.json +1 -1
- package/frontend/src/movie-app/components/EditorTabs/BatchVoiceVideoTab.tsx +0 -913
- package/frontend/src/movie-app/components/EditorTabs/CharacterSheet.tsx +0 -234
- package/frontend/src/movie-app/components/EditorTabs/ExtendVideoTab.tsx +0 -305
- package/frontend/src/movie-app/components/EditorTabs/ExtractImageTab.tsx +0 -249
- package/frontend/src/movie-app/components/EditorTabs/SceneVisualTab.tsx +0 -267
- package/frontend/src/movie-app/lib/batchVoiceStorage.ts +0 -75
- package/frontend/src/movie-app/stores/batchVoiceStore.ts +0 -990
- package/frontend/src/movie-app/stores/sceneVisualStore.ts +0 -251
|
@@ -2,20 +2,20 @@ import { useEffect, useState } from "react";
|
|
|
2
2
|
import { useParams, useNavigate } from "react-router-dom";
|
|
3
3
|
import { useProjectStore, type Project } from "../stores/projectStore";
|
|
4
4
|
import { useGenerationStore } from "../stores/generationStore";
|
|
5
|
-
import ExtendVideoTab from "./EditorTabs/ExtendVideoTab";
|
|
6
5
|
import FastImageEditTab from "./EditorTabs/FastImageEditTab";
|
|
7
6
|
import MovieStudioTab from "./EditorTabs/MovieStudioTab";
|
|
8
7
|
import GenerateVideoTab from "./EditorTabs/GenerateVideoTab";
|
|
9
8
|
import AgentTab from "./EditorTabs/AgentTab";
|
|
10
9
|
import StoryWriterTab from "./EditorTabs/StoryWriterTab";
|
|
11
|
-
import ExtractImageTab from "./EditorTabs/ExtractImageTab";
|
|
12
|
-
import SceneVisualTab from "./EditorTabs/SceneVisualTab";
|
|
13
10
|
import TextToImageTab from "./EditorTabs/TextToImageTab";
|
|
14
11
|
import BatchVideoTab from "./EditorTabs/BatchVideoTab";
|
|
15
12
|
import BatchImageToVideoTab from "./EditorTabs/BatchImageToVideoTab";
|
|
16
|
-
import BatchVoiceVideoTab from "./EditorTabs/BatchVoiceVideoTab";
|
|
17
13
|
import LlmServerTab from "./EditorTabs/LlmServerTab";
|
|
18
14
|
import SetupAiModelTab from "./EditorTabs/SetupAiModelTab";
|
|
15
|
+
import UpscaleTab from "./EditorTabs/UpscaleTab";
|
|
16
|
+
import VoiceCloneTab from "./EditorTabs/VoiceCloneTab";
|
|
17
|
+
import AudioToVideoTab from "./EditorTabs/AudioToVideoTab";
|
|
18
|
+
import AdvancedVoiceCloneTab from "./EditorTabs/AdvancedVoiceCloneTab";
|
|
19
19
|
|
|
20
20
|
export default function ProjectEditorPage() {
|
|
21
21
|
const { id } = useParams<{ id: string }>();
|
|
@@ -87,24 +87,6 @@ export default function ProjectEditorPage() {
|
|
|
87
87
|
</svg>
|
|
88
88
|
);
|
|
89
89
|
|
|
90
|
-
const ExtendIcon = (
|
|
91
|
-
<svg
|
|
92
|
-
width="18"
|
|
93
|
-
height="18"
|
|
94
|
-
viewBox="0 0 24 24"
|
|
95
|
-
fill="none"
|
|
96
|
-
stroke="currentColor"
|
|
97
|
-
strokeWidth="2"
|
|
98
|
-
strokeLinecap="round"
|
|
99
|
-
strokeLinejoin="round"
|
|
100
|
-
>
|
|
101
|
-
<polygon points="23 7 16 12 23 17 23 7" />
|
|
102
|
-
<rect x="1" y="5" width="15" height="14" rx="2" ry="2" />
|
|
103
|
-
<line x1="5" y1="12" x2="11" y2="12" />
|
|
104
|
-
<line x1="8" y1="9" x2="8" y2="15" />
|
|
105
|
-
</svg>
|
|
106
|
-
);
|
|
107
|
-
|
|
108
90
|
const FastImageEditIcon = (
|
|
109
91
|
<svg
|
|
110
92
|
width="18"
|
|
@@ -172,7 +154,7 @@ export default function ProjectEditorPage() {
|
|
|
172
154
|
</svg>
|
|
173
155
|
);
|
|
174
156
|
|
|
175
|
-
const
|
|
157
|
+
const TextToImageIcon = (
|
|
176
158
|
<svg
|
|
177
159
|
width="18"
|
|
178
160
|
height="18"
|
|
@@ -183,12 +165,11 @@ export default function ProjectEditorPage() {
|
|
|
183
165
|
strokeLinecap="round"
|
|
184
166
|
strokeLinejoin="round"
|
|
185
167
|
>
|
|
186
|
-
<
|
|
187
|
-
<circle cx="12" cy="13" r="4" />
|
|
168
|
+
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
|
|
188
169
|
</svg>
|
|
189
170
|
);
|
|
190
171
|
|
|
191
|
-
const
|
|
172
|
+
const BatchVideoIcon = (
|
|
192
173
|
<svg
|
|
193
174
|
width="18"
|
|
194
175
|
height="18"
|
|
@@ -200,12 +181,13 @@ export default function ProjectEditorPage() {
|
|
|
200
181
|
strokeLinejoin="round"
|
|
201
182
|
>
|
|
202
183
|
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
|
|
203
|
-
<
|
|
204
|
-
<
|
|
184
|
+
<line x1="3" y1="9" x2="21" y2="9" />
|
|
185
|
+
<line x1="3" y1="15" x2="21" y2="15" />
|
|
186
|
+
<line x1="9" y1="3" x2="9" y2="21" />
|
|
205
187
|
</svg>
|
|
206
188
|
);
|
|
207
189
|
|
|
208
|
-
const
|
|
190
|
+
const BatchImageToVideoIcon = (
|
|
209
191
|
<svg
|
|
210
192
|
width="18"
|
|
211
193
|
height="18"
|
|
@@ -216,11 +198,13 @@ export default function ProjectEditorPage() {
|
|
|
216
198
|
strokeLinecap="round"
|
|
217
199
|
strokeLinejoin="round"
|
|
218
200
|
>
|
|
219
|
-
<polygon points="12 2
|
|
201
|
+
<polygon points="12 2 2 7 12 12 22 7 12 2" />
|
|
202
|
+
<polyline points="2 17 12 22 22 17" />
|
|
203
|
+
<polyline points="2 12 12 17 22 12" />
|
|
220
204
|
</svg>
|
|
221
205
|
);
|
|
222
206
|
|
|
223
|
-
const
|
|
207
|
+
const ServerIcon = (
|
|
224
208
|
<svg
|
|
225
209
|
width="18"
|
|
226
210
|
height="18"
|
|
@@ -231,14 +215,26 @@ export default function ProjectEditorPage() {
|
|
|
231
215
|
strokeLinecap="round"
|
|
232
216
|
strokeLinejoin="round"
|
|
233
217
|
>
|
|
234
|
-
<rect x="
|
|
235
|
-
<
|
|
236
|
-
<line x1="
|
|
237
|
-
<line x1="
|
|
218
|
+
<rect x="2" y="2" width="20" height="8" rx="2" ry="2" />
|
|
219
|
+
<rect x="2" y="14" width="20" height="8" rx="2" ry="2" />
|
|
220
|
+
<line x1="6" y1="6" x2="6.01" y2="6" />
|
|
221
|
+
<line x1="6" y1="18" x2="6.01" y2="18" />
|
|
238
222
|
</svg>
|
|
239
223
|
);
|
|
240
224
|
|
|
241
|
-
const
|
|
225
|
+
const ServerStatusLight = () => (
|
|
226
|
+
<span
|
|
227
|
+
className={`w-2.5 h-2.5 rounded-full ${
|
|
228
|
+
store.agent.serverOnline === true
|
|
229
|
+
? "bg-emerald-500 shadow-[0_0_6px_2px_rgba(16,185,129,0.5)]"
|
|
230
|
+
: store.agent.serverOnline === false
|
|
231
|
+
? "bg-red-500 shadow-[0_0_6px_2px_rgba(239,68,68,0.5)]"
|
|
232
|
+
: "bg-tiffany-500"
|
|
233
|
+
}`}
|
|
234
|
+
/>
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const AiModelIcon = (
|
|
242
238
|
<svg
|
|
243
239
|
width="18"
|
|
244
240
|
height="18"
|
|
@@ -249,13 +245,20 @@ export default function ProjectEditorPage() {
|
|
|
249
245
|
strokeLinecap="round"
|
|
250
246
|
strokeLinejoin="round"
|
|
251
247
|
>
|
|
252
|
-
<
|
|
253
|
-
<
|
|
254
|
-
<line x1="
|
|
248
|
+
<rect x="4" y="4" width="16" height="16" rx="2" ry="2" />
|
|
249
|
+
<rect x="9" y="9" width="6" height="6" />
|
|
250
|
+
<line x1="9" y1="1" x2="9" y2="4" />
|
|
251
|
+
<line x1="15" y1="1" x2="15" y2="4" />
|
|
252
|
+
<line x1="9" y1="20" x2="9" y2="23" />
|
|
253
|
+
<line x1="15" y1="20" x2="15" y2="23" />
|
|
254
|
+
<line x1="20" y1="9" x2="23" y2="9" />
|
|
255
|
+
<line x1="20" y1="14" x2="23" y2="14" />
|
|
256
|
+
<line x1="1" y1="9" x2="4" y2="9" />
|
|
257
|
+
<line x1="1" y1="14" x2="4" y2="14" />
|
|
255
258
|
</svg>
|
|
256
259
|
);
|
|
257
260
|
|
|
258
|
-
const
|
|
261
|
+
const UpscaleIcon = (
|
|
259
262
|
<svg
|
|
260
263
|
width="18"
|
|
261
264
|
height="18"
|
|
@@ -266,13 +269,14 @@ export default function ProjectEditorPage() {
|
|
|
266
269
|
strokeLinecap="round"
|
|
267
270
|
strokeLinejoin="round"
|
|
268
271
|
>
|
|
269
|
-
<
|
|
270
|
-
<polyline points="
|
|
271
|
-
<
|
|
272
|
+
<polyline points="15 3 21 3 21 9" />
|
|
273
|
+
<polyline points="9 21 3 21 3 15" />
|
|
274
|
+
<line x1="21" y1="3" x2="14" y2="10" />
|
|
275
|
+
<line x1="3" y1="21" x2="10" y2="14" />
|
|
272
276
|
</svg>
|
|
273
277
|
);
|
|
274
278
|
|
|
275
|
-
const
|
|
279
|
+
const VoiceCloneIcon = (
|
|
276
280
|
<svg
|
|
277
281
|
width="18"
|
|
278
282
|
height="18"
|
|
@@ -283,26 +287,30 @@ export default function ProjectEditorPage() {
|
|
|
283
287
|
strokeLinecap="round"
|
|
284
288
|
strokeLinejoin="round"
|
|
285
289
|
>
|
|
286
|
-
<
|
|
287
|
-
<
|
|
288
|
-
<line x1="
|
|
289
|
-
<line x1="6" y1="18" x2="6.01" y2="18" />
|
|
290
|
+
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
|
|
291
|
+
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
|
|
292
|
+
<line x1="12" y1="19" x2="12" y2="23" />
|
|
290
293
|
</svg>
|
|
291
294
|
);
|
|
292
295
|
|
|
293
|
-
const
|
|
294
|
-
<
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
296
|
+
const AudioToVideoIcon = (
|
|
297
|
+
<svg
|
|
298
|
+
width="18"
|
|
299
|
+
height="18"
|
|
300
|
+
viewBox="0 0 24 24"
|
|
301
|
+
fill="none"
|
|
302
|
+
stroke="currentColor"
|
|
303
|
+
strokeWidth="2"
|
|
304
|
+
strokeLinecap="round"
|
|
305
|
+
strokeLinejoin="round"
|
|
306
|
+
>
|
|
307
|
+
<polygon points="23 7 16 12 23 17 23 7" />
|
|
308
|
+
<rect x="1" y="5" width="15" height="14" rx="2" ry="2" />
|
|
309
|
+
<line x1="4" y1="20" x2="8" y2="20" />
|
|
310
|
+
</svg>
|
|
303
311
|
);
|
|
304
312
|
|
|
305
|
-
const
|
|
313
|
+
const AdvancedVoiceCloneIcon = (
|
|
306
314
|
<svg
|
|
307
315
|
width="18"
|
|
308
316
|
height="18"
|
|
@@ -313,16 +321,11 @@ export default function ProjectEditorPage() {
|
|
|
313
321
|
strokeLinecap="round"
|
|
314
322
|
strokeLinejoin="round"
|
|
315
323
|
>
|
|
316
|
-
<
|
|
317
|
-
<
|
|
318
|
-
<line x1="
|
|
319
|
-
<
|
|
320
|
-
<
|
|
321
|
-
<line x1="15" y1="20" x2="15" y2="23" />
|
|
322
|
-
<line x1="20" y1="9" x2="23" y2="9" />
|
|
323
|
-
<line x1="20" y1="14" x2="23" y2="14" />
|
|
324
|
-
<line x1="1" y1="9" x2="4" y2="9" />
|
|
325
|
-
<line x1="1" y1="14" x2="4" y2="14" />
|
|
324
|
+
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
|
|
325
|
+
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
|
|
326
|
+
<line x1="12" y1="19" x2="12" y2="23" />
|
|
327
|
+
<path d="M5 3v4" />
|
|
328
|
+
<path d="M3 5h4" />
|
|
326
329
|
</svg>
|
|
327
330
|
);
|
|
328
331
|
|
|
@@ -408,18 +411,7 @@ export default function ProjectEditorPage() {
|
|
|
408
411
|
}`}
|
|
409
412
|
>
|
|
410
413
|
{VideoIcon}
|
|
411
|
-
|
|
412
|
-
</button>
|
|
413
|
-
<button
|
|
414
|
-
onClick={() => store.setActiveTab("extend")}
|
|
415
|
-
className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium rounded-2xl transition-all ${
|
|
416
|
-
store.activeTab === "extend"
|
|
417
|
-
? "bg-tiffany-500/10 text-tiffany-700 shadow-glow-sm"
|
|
418
|
-
: "text-ink-600 hover:bg-ink-100 hover:text-ink-800"
|
|
419
|
-
}`}
|
|
420
|
-
>
|
|
421
|
-
{ExtendIcon}
|
|
422
|
-
Extend Video
|
|
414
|
+
Scene Video Generation
|
|
423
415
|
</button>
|
|
424
416
|
<button
|
|
425
417
|
onClick={() => store.setActiveTab("fastImageEdit")}
|
|
@@ -455,82 +447,93 @@ export default function ProjectEditorPage() {
|
|
|
455
447
|
Story Writer
|
|
456
448
|
</button>
|
|
457
449
|
<button
|
|
458
|
-
onClick={() => store.setActiveTab("
|
|
450
|
+
onClick={() => store.setActiveTab("textToImage")}
|
|
459
451
|
className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium rounded-2xl transition-all ${
|
|
460
|
-
store.activeTab === "
|
|
452
|
+
store.activeTab === "textToImage"
|
|
461
453
|
? "bg-tiffany-500/10 text-tiffany-700 shadow-glow-sm"
|
|
462
454
|
: "text-ink-600 hover:bg-ink-100 hover:text-ink-800"
|
|
463
455
|
}`}
|
|
464
456
|
>
|
|
465
|
-
{
|
|
466
|
-
|
|
457
|
+
{TextToImageIcon}
|
|
458
|
+
Text-to-Image
|
|
467
459
|
</button>
|
|
468
460
|
<button
|
|
469
|
-
onClick={() => store.setActiveTab("
|
|
461
|
+
onClick={() => store.setActiveTab("batchVideo")}
|
|
470
462
|
className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium rounded-2xl transition-all ${
|
|
471
|
-
store.activeTab === "
|
|
463
|
+
store.activeTab === "batchVideo"
|
|
472
464
|
? "bg-tiffany-500/10 text-tiffany-700 shadow-glow-sm"
|
|
473
465
|
: "text-ink-600 hover:bg-ink-100 hover:text-ink-800"
|
|
474
466
|
}`}
|
|
475
467
|
>
|
|
476
|
-
{
|
|
477
|
-
|
|
468
|
+
{BatchVideoIcon}
|
|
469
|
+
Batch Video
|
|
478
470
|
</button>
|
|
479
471
|
<button
|
|
480
|
-
onClick={() => store.setActiveTab("
|
|
472
|
+
onClick={() => store.setActiveTab("batchImageToVideo")}
|
|
481
473
|
className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium rounded-2xl transition-all ${
|
|
482
|
-
store.activeTab === "
|
|
474
|
+
store.activeTab === "batchImageToVideo"
|
|
483
475
|
? "bg-tiffany-500/10 text-tiffany-700 shadow-glow-sm"
|
|
484
476
|
: "text-ink-600 hover:bg-ink-100 hover:text-ink-800"
|
|
485
477
|
}`}
|
|
486
478
|
>
|
|
487
|
-
{
|
|
488
|
-
|
|
479
|
+
{BatchImageToVideoIcon}
|
|
480
|
+
Batch Image to Video
|
|
489
481
|
</button>
|
|
490
482
|
<button
|
|
491
|
-
onClick={() => store.setActiveTab("
|
|
483
|
+
onClick={() => store.setActiveTab("llmServer")}
|
|
492
484
|
className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium rounded-2xl transition-all ${
|
|
493
|
-
store.activeTab === "
|
|
485
|
+
store.activeTab === "llmServer"
|
|
494
486
|
? "bg-tiffany-500/10 text-tiffany-700 shadow-glow-sm"
|
|
495
487
|
: "text-ink-600 hover:bg-ink-100 hover:text-ink-800"
|
|
496
488
|
}`}
|
|
497
489
|
>
|
|
498
|
-
{
|
|
499
|
-
|
|
490
|
+
{ServerIcon}
|
|
491
|
+
LLM Server
|
|
492
|
+
<ServerStatusLight />
|
|
500
493
|
</button>
|
|
501
494
|
<button
|
|
502
|
-
onClick={() => store.setActiveTab("
|
|
495
|
+
onClick={() => store.setActiveTab("upscale")}
|
|
503
496
|
className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium rounded-2xl transition-all ${
|
|
504
|
-
store.activeTab === "
|
|
497
|
+
store.activeTab === "upscale"
|
|
505
498
|
? "bg-tiffany-500/10 text-tiffany-700 shadow-glow-sm"
|
|
506
499
|
: "text-ink-600 hover:bg-ink-100 hover:text-ink-800"
|
|
507
500
|
}`}
|
|
508
501
|
>
|
|
509
|
-
{
|
|
510
|
-
|
|
502
|
+
{UpscaleIcon}
|
|
503
|
+
Upscale Media
|
|
511
504
|
</button>
|
|
512
505
|
<button
|
|
513
|
-
onClick={() => store.setActiveTab("
|
|
506
|
+
onClick={() => store.setActiveTab("voiceClone")}
|
|
514
507
|
className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium rounded-2xl transition-all ${
|
|
515
|
-
store.activeTab === "
|
|
508
|
+
store.activeTab === "voiceClone"
|
|
516
509
|
? "bg-tiffany-500/10 text-tiffany-700 shadow-glow-sm"
|
|
517
510
|
: "text-ink-600 hover:bg-ink-100 hover:text-ink-800"
|
|
518
511
|
}`}
|
|
519
512
|
>
|
|
520
|
-
{
|
|
521
|
-
|
|
513
|
+
{VoiceCloneIcon}
|
|
514
|
+
Voice Clone
|
|
522
515
|
</button>
|
|
523
516
|
<button
|
|
524
|
-
onClick={() => store.setActiveTab("
|
|
517
|
+
onClick={() => store.setActiveTab("advancedVoiceClone")}
|
|
525
518
|
className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium rounded-2xl transition-all ${
|
|
526
|
-
store.activeTab === "
|
|
519
|
+
store.activeTab === "advancedVoiceClone"
|
|
527
520
|
? "bg-tiffany-500/10 text-tiffany-700 shadow-glow-sm"
|
|
528
521
|
: "text-ink-600 hover:bg-ink-100 hover:text-ink-800"
|
|
529
522
|
}`}
|
|
530
523
|
>
|
|
531
|
-
{
|
|
532
|
-
|
|
533
|
-
|
|
524
|
+
{AdvancedVoiceCloneIcon}
|
|
525
|
+
Advanced Voice Clone
|
|
526
|
+
</button>
|
|
527
|
+
<button
|
|
528
|
+
onClick={() => store.setActiveTab("audioToVideo")}
|
|
529
|
+
className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium rounded-2xl transition-all ${
|
|
530
|
+
store.activeTab === "audioToVideo"
|
|
531
|
+
? "bg-tiffany-500/10 text-tiffany-700 shadow-glow-sm"
|
|
532
|
+
: "text-ink-600 hover:bg-ink-100 hover:text-ink-800"
|
|
533
|
+
}`}
|
|
534
|
+
>
|
|
535
|
+
{AudioToVideoIcon}
|
|
536
|
+
Audio to Video
|
|
534
537
|
</button>
|
|
535
538
|
<button
|
|
536
539
|
onClick={() => store.setActiveTab("aiModels")}
|
|
@@ -558,9 +561,6 @@ export default function ProjectEditorPage() {
|
|
|
558
561
|
{/* ========== VIDEO GENERATION PANEL ========== */}
|
|
559
562
|
{store.activeTab === "video" && <GenerateVideoTab projectId={id!} />}
|
|
560
563
|
|
|
561
|
-
{/* ========== EXTEND VIDEO PANEL ========== */}
|
|
562
|
-
{store.activeTab === "extend" && <ExtendVideoTab projectId={id!} />}
|
|
563
|
-
|
|
564
564
|
{/* ========== AGENT PANEL ========== */}
|
|
565
565
|
{store.activeTab === "agent" && <AgentTab projectId={id!} />}
|
|
566
566
|
|
|
@@ -569,14 +569,6 @@ export default function ProjectEditorPage() {
|
|
|
569
569
|
<StoryWriterTab projectId={id!} />
|
|
570
570
|
)}
|
|
571
571
|
|
|
572
|
-
{/* ========== EXTRACT IMAGE PANEL ========== */}
|
|
573
|
-
{store.activeTab === "extract" && <ExtractImageTab projectId={id!} />}
|
|
574
|
-
|
|
575
|
-
{/* ========== SCENE VISUAL PANEL ========== */}
|
|
576
|
-
{store.activeTab === "sceneVisual" && (
|
|
577
|
-
<SceneVisualTab projectId={id!} />
|
|
578
|
-
)}
|
|
579
|
-
|
|
580
572
|
{/* ========== TEXT-TO-IMAGE PANEL ========== */}
|
|
581
573
|
{store.activeTab === "textToImage" && (
|
|
582
574
|
<TextToImageTab projectId={id!} />
|
|
@@ -592,16 +584,29 @@ export default function ProjectEditorPage() {
|
|
|
592
584
|
<BatchImageToVideoTab projectId={id!} />
|
|
593
585
|
)}
|
|
594
586
|
|
|
595
|
-
{/* ========== BATCH VOICE VIDEO PANEL ========== */}
|
|
596
|
-
{store.activeTab === "batchVoice" && (
|
|
597
|
-
<BatchVoiceVideoTab projectId={id!} />
|
|
598
|
-
)}
|
|
599
|
-
|
|
600
587
|
{/* ========== LLM SERVER PANEL ========== */}
|
|
601
588
|
{store.activeTab === "llmServer" && <LlmServerTab />}
|
|
602
589
|
|
|
603
590
|
{/* ========== SETUP AI MODELS PANEL ========== */}
|
|
604
591
|
{store.activeTab === "aiModels" && <SetupAiModelTab />}
|
|
592
|
+
|
|
593
|
+
{/* ========== UPSCALE MEDIA PANEL ========== */}
|
|
594
|
+
{store.activeTab === "upscale" && <UpscaleTab projectId={id!} />}
|
|
595
|
+
|
|
596
|
+
{/* ========== VOICE CLONE PANEL ========== */}
|
|
597
|
+
{store.activeTab === "voiceClone" && (
|
|
598
|
+
<VoiceCloneTab projectId={id!} />
|
|
599
|
+
)}
|
|
600
|
+
|
|
601
|
+
{/* ========== ADVANCED VOICE CLONE PANEL ========== */}
|
|
602
|
+
{store.activeTab === "advancedVoiceClone" && (
|
|
603
|
+
<AdvancedVoiceCloneTab projectId={id!} />
|
|
604
|
+
)}
|
|
605
|
+
|
|
606
|
+
{/* ========== AUDIO TO VIDEO PANEL ========== */}
|
|
607
|
+
{store.activeTab === "audioToVideo" && (
|
|
608
|
+
<AudioToVideoTab projectId={id!} />
|
|
609
|
+
)}
|
|
605
610
|
</div>
|
|
606
611
|
</div>
|
|
607
612
|
</div>
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
import type { QueueTask } from "./queueStore";
|
|
3
|
+
|
|
4
|
+
const API_BASE = `http://localhost:${(window as any).PORT}`;
|
|
5
|
+
|
|
6
|
+
export interface VoiceAudio {
|
|
7
|
+
filename: string;
|
|
8
|
+
url: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function resolveUrl(url: string): string {
|
|
12
|
+
return url.startsWith("http") ? url : `${API_BASE}${url}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface AdvancedVoiceCloneStore {
|
|
16
|
+
text: string;
|
|
17
|
+
language: string;
|
|
18
|
+
outPrefix: string;
|
|
19
|
+
model: string;
|
|
20
|
+
refAudio: VoiceAudio | null;
|
|
21
|
+
audios: VoiceAudio[];
|
|
22
|
+
audiosLoading: boolean;
|
|
23
|
+
uploading: boolean;
|
|
24
|
+
generating: boolean;
|
|
25
|
+
result: string | null;
|
|
26
|
+
error: string | null;
|
|
27
|
+
|
|
28
|
+
setText: (t: string) => void;
|
|
29
|
+
setLanguage: (l: string) => void;
|
|
30
|
+
setOutPrefix: (p: string) => void;
|
|
31
|
+
setModel: (m: string) => void;
|
|
32
|
+
setRefAudio: (a: VoiceAudio | null) => void;
|
|
33
|
+
clearResult: () => void;
|
|
34
|
+
fetchAudios: (projectId: string) => Promise<void>;
|
|
35
|
+
uploadAudio: (
|
|
36
|
+
projectId: string,
|
|
37
|
+
base64: string,
|
|
38
|
+
filename: string,
|
|
39
|
+
) => Promise<void>;
|
|
40
|
+
generate: (projectId: string) => Promise<void>;
|
|
41
|
+
applyQueueTask: (task: QueueTask) => void;
|
|
42
|
+
reset: () => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The queue task id enqueued by the current generate action, if any. */
|
|
46
|
+
let advancedVoiceCloneActiveTaskId: string | null = null;
|
|
47
|
+
|
|
48
|
+
async function enqueueAdvancedVoiceCloneTask(
|
|
49
|
+
projectId: string,
|
|
50
|
+
text: string,
|
|
51
|
+
refAudioPath: string,
|
|
52
|
+
language: string,
|
|
53
|
+
outPrefix: string,
|
|
54
|
+
model: string,
|
|
55
|
+
): Promise<{ ok: boolean; error?: string; taskId?: string }> {
|
|
56
|
+
try {
|
|
57
|
+
const res = await fetch(`${API_BASE}/api/queue/enqueue`, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: { "Content-Type": "application/json" },
|
|
60
|
+
body: JSON.stringify({
|
|
61
|
+
projectId,
|
|
62
|
+
type: "advanced-voice-clone",
|
|
63
|
+
label: "Advanced voice clone",
|
|
64
|
+
payload: { text, refAudioPath, language, outPrefix, model },
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
return { ok: false, error: await res.text() };
|
|
69
|
+
}
|
|
70
|
+
const task = (await res.json()) as { id?: string };
|
|
71
|
+
return { ok: true, taskId: task.id };
|
|
72
|
+
} catch (e) {
|
|
73
|
+
return { ok: false, error: String(e) };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const useAdvancedVoiceCloneStore = create<AdvancedVoiceCloneStore>(
|
|
78
|
+
(set, get) => ({
|
|
79
|
+
text: "",
|
|
80
|
+
language: "YUE",
|
|
81
|
+
outPrefix: "voice",
|
|
82
|
+
model: "./dots-tts-mlx-weights/mf-int4",
|
|
83
|
+
refAudio: null,
|
|
84
|
+
audios: [],
|
|
85
|
+
audiosLoading: false,
|
|
86
|
+
uploading: false,
|
|
87
|
+
generating: false,
|
|
88
|
+
result: null,
|
|
89
|
+
error: null,
|
|
90
|
+
|
|
91
|
+
setText: (text) => set({ text, error: null }),
|
|
92
|
+
setLanguage: (language) => set({ language, error: null }),
|
|
93
|
+
setOutPrefix: (outPrefix) => set({ outPrefix, error: null }),
|
|
94
|
+
setModel: (model) => set({ model, error: null }),
|
|
95
|
+
setRefAudio: (refAudio) => set({ refAudio, error: null }),
|
|
96
|
+
clearResult: () => set({ result: null, error: null }),
|
|
97
|
+
|
|
98
|
+
fetchAudios: async (projectId) => {
|
|
99
|
+
set({ audiosLoading: true });
|
|
100
|
+
try {
|
|
101
|
+
const res = await fetch(`${API_BASE}/api/projects/${projectId}/audios`);
|
|
102
|
+
if (!res.ok) throw new Error(await res.text());
|
|
103
|
+
const audios: VoiceAudio[] = await res.json();
|
|
104
|
+
set({
|
|
105
|
+
audios: audios.map((a) => ({ ...a, url: resolveUrl(a.url) })),
|
|
106
|
+
audiosLoading: false,
|
|
107
|
+
});
|
|
108
|
+
} catch {
|
|
109
|
+
set({ audiosLoading: false });
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
uploadAudio: async (projectId, base64, filename) => {
|
|
114
|
+
set({ uploading: true, error: null });
|
|
115
|
+
try {
|
|
116
|
+
const res = await fetch(`${API_BASE}/api/upload/audio`, {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: { "Content-Type": "application/json" },
|
|
119
|
+
body: JSON.stringify({ audio: base64, filename, projectId }),
|
|
120
|
+
});
|
|
121
|
+
if (!res.ok) {
|
|
122
|
+
set({ uploading: false, error: await res.text() });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const data = await res.json();
|
|
126
|
+
set({
|
|
127
|
+
uploading: false,
|
|
128
|
+
refAudio: {
|
|
129
|
+
filename: data.filename,
|
|
130
|
+
url: resolveUrl(
|
|
131
|
+
`/api/files?path=${encodeURIComponent(data.path)}`,
|
|
132
|
+
),
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
void get().fetchAudios(projectId);
|
|
136
|
+
} catch (e) {
|
|
137
|
+
set({ uploading: false, error: String(e) });
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
generate: async (projectId) => {
|
|
142
|
+
const { text, refAudio, language, outPrefix, model, generating } = get();
|
|
143
|
+
if (generating || !refAudio || !text.trim()) return;
|
|
144
|
+
|
|
145
|
+
set({ generating: true, error: null, result: null });
|
|
146
|
+
|
|
147
|
+
const r = await enqueueAdvancedVoiceCloneTask(
|
|
148
|
+
projectId,
|
|
149
|
+
text.trim(),
|
|
150
|
+
refAudio.filename,
|
|
151
|
+
language,
|
|
152
|
+
outPrefix,
|
|
153
|
+
model,
|
|
154
|
+
);
|
|
155
|
+
advancedVoiceCloneActiveTaskId = r.taskId ?? null;
|
|
156
|
+
if (!r.ok) {
|
|
157
|
+
set({
|
|
158
|
+
generating: false,
|
|
159
|
+
error: r.error ?? "Failed to enqueue advanced voice clone",
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
// Reconcile state with the latest queue task state. Only the task enqueued by
|
|
165
|
+
// this tab is reflected, so past tasks never surface stale results.
|
|
166
|
+
applyQueueTask: (task) => {
|
|
167
|
+
if (task.type !== "advanced-voice-clone") return;
|
|
168
|
+
if (task.id !== advancedVoiceCloneActiveTaskId) return;
|
|
169
|
+
|
|
170
|
+
if (task.status === "completed") {
|
|
171
|
+
const url = task.result?.url;
|
|
172
|
+
set({
|
|
173
|
+
generating: false,
|
|
174
|
+
result: url ? resolveUrl(url) : null,
|
|
175
|
+
error: null,
|
|
176
|
+
});
|
|
177
|
+
} else if (task.status === "failed") {
|
|
178
|
+
set({
|
|
179
|
+
generating: false,
|
|
180
|
+
error: task.error ?? "Advanced voice clone failed",
|
|
181
|
+
});
|
|
182
|
+
} else if (task.status === "cancelled" || task.status === "paused") {
|
|
183
|
+
set({ generating: false, error: null });
|
|
184
|
+
} else {
|
|
185
|
+
// pending / running
|
|
186
|
+
set({ generating: true, error: null });
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
reset: () => {
|
|
191
|
+
advancedVoiceCloneActiveTaskId = null;
|
|
192
|
+
set({
|
|
193
|
+
text: "",
|
|
194
|
+
language: "YUE",
|
|
195
|
+
outPrefix: "voice",
|
|
196
|
+
model: "./dots-tts-mlx-weights/mf-int4",
|
|
197
|
+
refAudio: null,
|
|
198
|
+
audios: [],
|
|
199
|
+
audiosLoading: false,
|
|
200
|
+
uploading: false,
|
|
201
|
+
generating: false,
|
|
202
|
+
result: null,
|
|
203
|
+
error: null,
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
}),
|
|
207
|
+
);
|