@effectnode/media 0.4.0 → 0.6.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/agent-backend.d.ts +1 -1
- package/dist/backend/movie-backend/agent/agent-backend.js +169 -83
- package/dist/backend/movie-backend/agent/tools/index.js +0 -2
- package/dist/backend/movie-backend/core.js +1 -1
- package/dist/backend/movie-backend/generation-queue.js +107 -44
- package/dist/backend/movie-backend/render-media.js +28 -254
- package/frontend/index.html +6 -1
- package/frontend/public/lambobo.png +0 -0
- package/frontend/src/movie-app/MediaStudio.tsx +6 -2
- package/frontend/src/movie-app/SetupPage.tsx +98 -63
- package/frontend/src/movie-app/components/Aurora.tsx +13 -0
- package/frontend/src/movie-app/components/EditorTabs/GenerateVideoTab.tsx +5 -8
- package/frontend/src/movie-app/components/EditorTabs/MovieStudioTab.tsx +417 -205
- package/frontend/src/movie-app/components/EditorTabs/SetupAiModelTab.tsx +350 -0
- package/frontend/src/movie-app/components/ProjectEditorPage.tsx +40 -67
- package/frontend/src/movie-app/components/ProjectManager.tsx +80 -52
- package/frontend/src/movie-app/index.css +275 -8
- package/frontend/src/movie-app/index.html +1 -1
- package/frontend/src/movie-app/stores/aiModelStore.ts +172 -0
- package/frontend/src/movie-app/stores/generationStore.ts +2 -3
- package/frontend/src/movie-app/stores/movieStudioStore.ts +72 -3
- package/package.json +2 -2
- package/frontend/src/movie-app/components/EditorTabs/CharactersTab.tsx +0 -664
- package/frontend/src/movie-app/components/EditorTabs/ReferencesToVideoTab.tsx +0 -881
|
@@ -0,0 +1,350 @@
|
|
|
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: "ltx",
|
|
195
|
+
name: "dgrauet/ltx-2.3-mlx-q8",
|
|
196
|
+
desc: "Video generation",
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: "tts",
|
|
200
|
+
name: "Qwen/Qwen3-TTS-12Hz-1.7B-Base",
|
|
201
|
+
desc: "Voice-over & speech",
|
|
202
|
+
},
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
export default function SetupAiModelTab() {
|
|
206
|
+
const store = useAiModelStore();
|
|
207
|
+
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
store.checkStatus();
|
|
210
|
+
const interval = setInterval(() => store.checkStatus(), 3000);
|
|
211
|
+
return () => clearInterval(interval);
|
|
212
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
213
|
+
}, []);
|
|
214
|
+
|
|
215
|
+
const busy = store.downloading !== null;
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<div className="flex flex-col gap-6">
|
|
219
|
+
{/* Header */}
|
|
220
|
+
<div className="flex items-center gap-2">
|
|
221
|
+
<span className="text-tiffany-600">{ChipIcon}</span>
|
|
222
|
+
<h2 className="text-base font-semibold text-ink-900">AI Models</h2>
|
|
223
|
+
<button
|
|
224
|
+
onClick={() => store.checkStatus()}
|
|
225
|
+
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"
|
|
226
|
+
>
|
|
227
|
+
{RefreshIcon}
|
|
228
|
+
Refresh
|
|
229
|
+
</button>
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
{/* Engines */}
|
|
233
|
+
<section>
|
|
234
|
+
<h3 className="mb-2 text-sm font-semibold text-ink-900">Engines</h3>
|
|
235
|
+
<div className="space-y-2">
|
|
236
|
+
<ModelRow name="mlx-gen" desc="Image generation engine (z-image · flux)">
|
|
237
|
+
<StatusBadge
|
|
238
|
+
value={store.mlxgenInstalled}
|
|
239
|
+
okText="Installed"
|
|
240
|
+
missingText="Not installed"
|
|
241
|
+
/>
|
|
242
|
+
<button
|
|
243
|
+
onClick={() => store.installTool("mlxgen")}
|
|
244
|
+
disabled={busy}
|
|
245
|
+
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"
|
|
246
|
+
>
|
|
247
|
+
{store.downloading === "mlxgen" ? SpinnerIcon : InstallIcon}
|
|
248
|
+
{store.downloading === "mlxgen" ? "Installing…" : "Install"}
|
|
249
|
+
</button>
|
|
250
|
+
</ModelRow>
|
|
251
|
+
|
|
252
|
+
<ModelRow name="mlx-vlm" desc="LLM server (gemma) for story planning">
|
|
253
|
+
<StatusBadge
|
|
254
|
+
value={store.mlxVlmInstalled}
|
|
255
|
+
okText="Installed"
|
|
256
|
+
missingText="Not installed"
|
|
257
|
+
/>
|
|
258
|
+
<button
|
|
259
|
+
onClick={() => store.installTool("mlx-vlm")}
|
|
260
|
+
disabled={busy}
|
|
261
|
+
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"
|
|
262
|
+
>
|
|
263
|
+
{store.downloading === "mlx-vlm" ? SpinnerIcon : InstallIcon}
|
|
264
|
+
{store.downloading === "mlx-vlm" ? "Installing…" : "Install"}
|
|
265
|
+
</button>
|
|
266
|
+
</ModelRow>
|
|
267
|
+
|
|
268
|
+
<ModelRow name="huggingface-cli" desc="HF CLI for downloading models">
|
|
269
|
+
<StatusBadge
|
|
270
|
+
value={store.hfInstalled}
|
|
271
|
+
okText="Installed"
|
|
272
|
+
missingText="Not installed"
|
|
273
|
+
/>
|
|
274
|
+
<button
|
|
275
|
+
onClick={() => store.installTool("hf-cli")}
|
|
276
|
+
disabled={busy}
|
|
277
|
+
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"
|
|
278
|
+
>
|
|
279
|
+
{store.downloading === "hf-cli" ? SpinnerIcon : InstallIcon}
|
|
280
|
+
{store.downloading === "hf-cli" ? "Installing…" : "Install"}
|
|
281
|
+
</button>
|
|
282
|
+
</ModelRow>
|
|
283
|
+
</div>
|
|
284
|
+
</section>
|
|
285
|
+
|
|
286
|
+
{/* Models */}
|
|
287
|
+
<section>
|
|
288
|
+
<h3 className="mb-2 text-sm font-semibold text-ink-900">Models</h3>
|
|
289
|
+
<div className="space-y-2">
|
|
290
|
+
<ModelRow
|
|
291
|
+
name="mlx-community/gemma-4-e2b-it-4bit"
|
|
292
|
+
desc="Story planning & dialogue (LLM)"
|
|
293
|
+
>
|
|
294
|
+
<StatusBadge
|
|
295
|
+
value={store.mlxVlmInstalled}
|
|
296
|
+
okText="Ready"
|
|
297
|
+
missingText="Not set up"
|
|
298
|
+
/>
|
|
299
|
+
<span className="text-xs italic text-ink-400">
|
|
300
|
+
Downloads on first server start
|
|
301
|
+
</span>
|
|
302
|
+
</ModelRow>
|
|
303
|
+
|
|
304
|
+
{DOWNLOAD_MODELS.map((m) => {
|
|
305
|
+
const downloaded =
|
|
306
|
+
m.id === "z-image"
|
|
307
|
+
? store.zImageDownloaded
|
|
308
|
+
: m.id === "flux"
|
|
309
|
+
? store.fluxDownloaded
|
|
310
|
+
: m.id === "ltx"
|
|
311
|
+
? store.ltxDownloaded
|
|
312
|
+
: store.ttsDownloaded;
|
|
313
|
+
return (
|
|
314
|
+
<ModelRow key={m.id} name={m.name} desc={m.desc}>
|
|
315
|
+
<StatusBadge value={downloaded} />
|
|
316
|
+
<button
|
|
317
|
+
onClick={() => store.downloadModel(m.id)}
|
|
318
|
+
disabled={busy}
|
|
319
|
+
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"
|
|
320
|
+
>
|
|
321
|
+
{store.downloading === m.id ? SpinnerIcon : DownloadIcon}
|
|
322
|
+
{store.downloading === m.id ? "Downloading…" : "Download"}
|
|
323
|
+
</button>
|
|
324
|
+
</ModelRow>
|
|
325
|
+
);
|
|
326
|
+
})}
|
|
327
|
+
</div>
|
|
328
|
+
</section>
|
|
329
|
+
|
|
330
|
+
{/* Error */}
|
|
331
|
+
{store.error && (
|
|
332
|
+
<div className="rounded-2xl border border-rose-200 bg-rose-50/70 px-4 py-3 text-xs text-rose-600">
|
|
333
|
+
{store.error}
|
|
334
|
+
</div>
|
|
335
|
+
)}
|
|
336
|
+
|
|
337
|
+
{/* Download logs */}
|
|
338
|
+
{store.logs.length > 0 && (
|
|
339
|
+
<div className="rounded-2xl border border-ink-200 bg-ink-50 p-4">
|
|
340
|
+
<p className="mb-2 text-[11px] font-semibold uppercase tracking-wider text-ink-500">
|
|
341
|
+
Output
|
|
342
|
+
</p>
|
|
343
|
+
<pre className="max-h-48 overflow-y-auto whitespace-pre-wrap font-mono text-xs leading-relaxed text-ink-700">
|
|
344
|
+
{store.logs.join("")}
|
|
345
|
+
</pre>
|
|
346
|
+
</div>
|
|
347
|
+
)}
|
|
348
|
+
</div>
|
|
349
|
+
);
|
|
350
|
+
}
|
|
@@ -8,15 +8,14 @@ import MovieStudioTab from "./EditorTabs/MovieStudioTab";
|
|
|
8
8
|
import GenerateVideoTab from "./EditorTabs/GenerateVideoTab";
|
|
9
9
|
import AgentTab from "./EditorTabs/AgentTab";
|
|
10
10
|
import StoryWriterTab from "./EditorTabs/StoryWriterTab";
|
|
11
|
-
import CharactersTab from "./EditorTabs/CharactersTab";
|
|
12
11
|
import ExtractImageTab from "./EditorTabs/ExtractImageTab";
|
|
13
12
|
import SceneVisualTab from "./EditorTabs/SceneVisualTab";
|
|
14
13
|
import TextToImageTab from "./EditorTabs/TextToImageTab";
|
|
15
|
-
import ReferencesToVideoTab from "./EditorTabs/ReferencesToVideoTab";
|
|
16
14
|
import BatchVideoTab from "./EditorTabs/BatchVideoTab";
|
|
17
15
|
import BatchImageToVideoTab from "./EditorTabs/BatchImageToVideoTab";
|
|
18
16
|
import BatchVoiceVideoTab from "./EditorTabs/BatchVoiceVideoTab";
|
|
19
17
|
import LlmServerTab from "./EditorTabs/LlmServerTab";
|
|
18
|
+
import SetupAiModelTab from "./EditorTabs/SetupAiModelTab";
|
|
20
19
|
|
|
21
20
|
export default function ProjectEditorPage() {
|
|
22
21
|
const { id } = useParams<{ id: string }>();
|
|
@@ -173,22 +172,6 @@ export default function ProjectEditorPage() {
|
|
|
173
172
|
</svg>
|
|
174
173
|
);
|
|
175
174
|
|
|
176
|
-
const CharacterIcon = (
|
|
177
|
-
<svg
|
|
178
|
-
width="18"
|
|
179
|
-
height="18"
|
|
180
|
-
viewBox="0 0 24 24"
|
|
181
|
-
fill="none"
|
|
182
|
-
stroke="currentColor"
|
|
183
|
-
strokeWidth="2"
|
|
184
|
-
strokeLinecap="round"
|
|
185
|
-
strokeLinejoin="round"
|
|
186
|
-
>
|
|
187
|
-
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
|
188
|
-
<circle cx="12" cy="7" r="4" />
|
|
189
|
-
</svg>
|
|
190
|
-
);
|
|
191
|
-
|
|
192
175
|
const ExtractIcon = (
|
|
193
176
|
<svg
|
|
194
177
|
width="18"
|
|
@@ -237,22 +220,6 @@ export default function ProjectEditorPage() {
|
|
|
237
220
|
</svg>
|
|
238
221
|
);
|
|
239
222
|
|
|
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
223
|
const BatchVideoIcon = (
|
|
257
224
|
<svg
|
|
258
225
|
width="18"
|
|
@@ -335,6 +302,30 @@ export default function ProjectEditorPage() {
|
|
|
335
302
|
/>
|
|
336
303
|
);
|
|
337
304
|
|
|
305
|
+
const AiModelIcon = (
|
|
306
|
+
<svg
|
|
307
|
+
width="18"
|
|
308
|
+
height="18"
|
|
309
|
+
viewBox="0 0 24 24"
|
|
310
|
+
fill="none"
|
|
311
|
+
stroke="currentColor"
|
|
312
|
+
strokeWidth="2"
|
|
313
|
+
strokeLinecap="round"
|
|
314
|
+
strokeLinejoin="round"
|
|
315
|
+
>
|
|
316
|
+
<rect x="4" y="4" width="16" height="16" rx="2" ry="2" />
|
|
317
|
+
<rect x="9" y="9" width="6" height="6" />
|
|
318
|
+
<line x1="9" y1="1" x2="9" y2="4" />
|
|
319
|
+
<line x1="15" y1="1" x2="15" y2="4" />
|
|
320
|
+
<line x1="9" y1="20" x2="9" y2="23" />
|
|
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" />
|
|
326
|
+
</svg>
|
|
327
|
+
);
|
|
328
|
+
|
|
338
329
|
// ========== Loading / Not Found ==========
|
|
339
330
|
|
|
340
331
|
if (!project) {
|
|
@@ -417,7 +408,7 @@ export default function ProjectEditorPage() {
|
|
|
417
408
|
}`}
|
|
418
409
|
>
|
|
419
410
|
{VideoIcon}
|
|
420
|
-
Generate Avatar
|
|
411
|
+
Generate Avatar Video
|
|
421
412
|
</button>
|
|
422
413
|
<button
|
|
423
414
|
onClick={() => store.setActiveTab("extend")}
|
|
@@ -463,17 +454,6 @@ export default function ProjectEditorPage() {
|
|
|
463
454
|
{StoryWriterIcon}
|
|
464
455
|
Story Writer
|
|
465
456
|
</button>
|
|
466
|
-
<button
|
|
467
|
-
onClick={() => store.setActiveTab("characters")}
|
|
468
|
-
className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium rounded-2xl transition-all ${
|
|
469
|
-
store.activeTab === "characters"
|
|
470
|
-
? "bg-tiffany-500/10 text-tiffany-700 shadow-glow-sm"
|
|
471
|
-
: "text-ink-600 hover:bg-ink-100 hover:text-ink-800"
|
|
472
|
-
}`}
|
|
473
|
-
>
|
|
474
|
-
{CharacterIcon}
|
|
475
|
-
Characters
|
|
476
|
-
</button>
|
|
477
457
|
<button
|
|
478
458
|
onClick={() => store.setActiveTab("extract")}
|
|
479
459
|
className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium rounded-2xl transition-all ${
|
|
@@ -507,17 +487,6 @@ export default function ProjectEditorPage() {
|
|
|
507
487
|
{TextToImageIcon}
|
|
508
488
|
Text-to-Image
|
|
509
489
|
</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
490
|
<button
|
|
522
491
|
onClick={() => store.setActiveTab("batchVideo")}
|
|
523
492
|
className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium rounded-2xl transition-all ${
|
|
@@ -563,6 +532,17 @@ export default function ProjectEditorPage() {
|
|
|
563
532
|
LLM Server
|
|
564
533
|
<ServerStatusLight />
|
|
565
534
|
</button>
|
|
535
|
+
<button
|
|
536
|
+
onClick={() => store.setActiveTab("aiModels")}
|
|
537
|
+
className={`flex items-center gap-2 px-5 py-2.5 text-sm font-medium rounded-2xl transition-all ${
|
|
538
|
+
store.activeTab === "aiModels"
|
|
539
|
+
? "bg-tiffany-500/10 text-tiffany-700 shadow-glow-sm"
|
|
540
|
+
: "text-ink-600 hover:bg-ink-100 hover:text-ink-800"
|
|
541
|
+
}`}
|
|
542
|
+
>
|
|
543
|
+
{AiModelIcon}
|
|
544
|
+
Setup AI Models
|
|
545
|
+
</button>
|
|
566
546
|
</div>
|
|
567
547
|
|
|
568
548
|
{/* ========== MOVIE STUDIO PANEL ========== */}
|
|
@@ -589,11 +569,6 @@ export default function ProjectEditorPage() {
|
|
|
589
569
|
<StoryWriterTab projectId={id!} />
|
|
590
570
|
)}
|
|
591
571
|
|
|
592
|
-
{/* ========== CHARACTERS PANEL ========== */}
|
|
593
|
-
{store.activeTab === "characters" && (
|
|
594
|
-
<CharactersTab projectId={id!} />
|
|
595
|
-
)}
|
|
596
|
-
|
|
597
572
|
{/* ========== EXTRACT IMAGE PANEL ========== */}
|
|
598
573
|
{store.activeTab === "extract" && <ExtractImageTab projectId={id!} />}
|
|
599
574
|
|
|
@@ -607,11 +582,6 @@ export default function ProjectEditorPage() {
|
|
|
607
582
|
<TextToImageTab projectId={id!} />
|
|
608
583
|
)}
|
|
609
584
|
|
|
610
|
-
{/* ========== REFERENCES TO VIDEO PANEL ========== */}
|
|
611
|
-
{store.activeTab === "referencesToVideo" && (
|
|
612
|
-
<ReferencesToVideoTab projectId={id!} />
|
|
613
|
-
)}
|
|
614
|
-
|
|
615
585
|
{/* ========== BATCH VIDEO PANEL ========== */}
|
|
616
586
|
{store.activeTab === "batchVideo" && (
|
|
617
587
|
<BatchVideoTab projectId={id!} />
|
|
@@ -629,6 +599,9 @@ export default function ProjectEditorPage() {
|
|
|
629
599
|
|
|
630
600
|
{/* ========== LLM SERVER PANEL ========== */}
|
|
631
601
|
{store.activeTab === "llmServer" && <LlmServerTab />}
|
|
602
|
+
|
|
603
|
+
{/* ========== SETUP AI MODELS PANEL ========== */}
|
|
604
|
+
{store.activeTab === "aiModels" && <SetupAiModelTab />}
|
|
632
605
|
</div>
|
|
633
606
|
</div>
|
|
634
607
|
</div>
|