Spaces:
Running
Running
Download frontend/src/pages/CreateVideo.jsx from tntgroup/website_content: direct link, hf CLI and curl.
- Browser
- Download file 5.96 kB
-
https://huggingface.co/spaces/tntgroup/website_content/resolve/main/frontend/src/pages/CreateVideo.jsx
- Command line
-
hf download hf://spaces/tntgroup/website_content/frontend/src/pages/CreateVideo.jsx
-
curl -L -o CreateVideo.jsx https://huggingface.co/spaces/tntgroup/website_content/resolve/main/frontend/src/pages/CreateVideo.jsx
5.96 kB
| import { useState, useEffect } from "react"; | |
| import { useVideoGen } from "../stores/videoGenStore"; | |
| import track from "../lib/track"; | |
| import { useDirty } from "../stores/dirtyStore"; | |
| import { IcVideo } from "../components/Icons"; | |
| import { jget, jpost, request } from "../api"; | |
| export default function CreateVideo() { | |
| const { connected, url, connect, disconnect } = useVideoGen(); | |
| const { makeDirty, makeClean } = useDirty(); | |
| const [connectUrl, setConnectUrl] = useState(url || ""); | |
| const [busy, setBusy] = useState(false); | |
| useEffect(() => { | |
| jget("/api/video/status") | |
| .then(data => { | |
| if (data.connected && data.url) { | |
| setConnectUrl(data.url); | |
| connect(data.url, data); | |
| } | |
| }) | |
| .catch(() => {}); | |
| }, []); | |
| const handleConnect = async () => { | |
| makeDirty("Bạn đang kết nối tạo video. Mất tiến trình nếu rời đi."); | |
| if (!connectUrl.trim()) return; | |
| setBusy(true); | |
| // Tool này chỉ bàn giao sang app ngoài: đo được tới đây là hết, KHÔNG có | |
| // output nội bộ để đo (spec §4.8). | |
| const t0 = Date.now(); | |
| track.runClick({ phase: "connect" }); | |
| try { | |
| const data = await jpost("/api/video/connect", { url: connectUrl }); | |
| connect(connectUrl, data); | |
| track.step("connect_success", "output", { latency_ms_client: Date.now() - t0 }); | |
| } catch (e) { | |
| track.abandon("connect_failed", "run"); | |
| alert("Lỗi kết nối: " + e.message); | |
| } finally { | |
| setBusy(false); | |
| } | |
| }; | |
| const handleDisconnect = async () => { | |
| makeClean(); | |
| setBusy(true); | |
| try { | |
| await request("/api/video/disconnect", { method: "POST" }); | |
| disconnect(); | |
| } catch (e) { | |
| alert("Lỗi: " + e.message); | |
| } finally { | |
| setBusy(false); | |
| } | |
| }; | |
| return ( | |
| <> | |
| <div style={{ | |
| display: "flex", alignItems: "center", gap: 12, | |
| padding: "10px 36px", background: "var(--white)", | |
| borderBottom: "1px solid var(--line)", | |
| flex: "0 0 auto", | |
| }}> | |
| <IcVideo size={18} style={{ color: "var(--orange)" }} /> | |
| <span style={{ | |
| display: "inline-flex", alignItems: "center", gap: 5, | |
| fontSize: 12, fontWeight: 600, padding: "4px 10px", borderRadius: 20, | |
| background: connected ? "var(--green-soft)" : "var(--red-soft)", | |
| color: connected ? "var(--green)" : "var(--red)", | |
| }}> | |
| <span style={{ width: 6, height: 6, borderRadius: "50%", background: "currentColor" }} /> | |
| {connected ? "Đã kết nối" : "Chưa kết nối"} | |
| </span> | |
| {!connected ? ( | |
| <> | |
| <input type="text" value={connectUrl} | |
| onChange={e => setConnectUrl(e.target.value)} | |
| placeholder="https://xxxx.gradio.live/" | |
| style={{ flex: 1, maxWidth: 400, padding: "7px 12px", fontSize: 13, border: "2px solid var(--line)", borderRadius: "var(--radius-sm)", background: "#fff" }} /> | |
| <button className="btn primary sm" disabled={busy || !connectUrl.trim()} | |
| onClick={handleConnect}> | |
| {busy ? "⏳" : "🔌 Kết nối"} | |
| </button> | |
| </> | |
| ) : ( | |
| <> | |
| <span className="small muted" style={{ flex: 1 }}>{url}</span> | |
| <button className="btn sm" disabled={busy} onClick={handleDisconnect}> | |
| 🔌 Ngắt kết nối | |
| </button> | |
| </> | |
| )} | |
| </div> | |
| {/* Hướng dẫn khi chưa kết nối */} | |
| {!connected && ( | |
| <div style={{ | |
| flex: 1, display: "flex", alignItems: "center", justifyContent: "center", | |
| background: "var(--cream)", | |
| }}> | |
| <div style={{ textAlign: "center", maxWidth: 480 }}> | |
| <div style={{ | |
| width: 64, height: 64, borderRadius: "50%", | |
| background: "var(--orange-soft)", color: "var(--orange)", | |
| display: "grid", placeItems: "center", margin: "0 auto 16px", | |
| }}> | |
| <IcVideo size={28} /> | |
| </div> | |
| <h2 style={{ margin: "0 0 8px", fontSize: 20 }}>Tạo video với AI</h2> | |
| <p style={{ color: "var(--ink-2)", fontSize: 14, lineHeight: 1.6, margin: "0 0 20px" }}> | |
| Kết nối tới server WanGP đang chạy để dùng giao diện tạo video gốc ngay trong trang. | |
| </p> | |
| <ol style={{ | |
| textAlign: "left", margin: "0 auto", padding: 0, listStyle: "none", | |
| display: "flex", flexDirection: "column", gap: 12, | |
| }}> | |
| {[ | |
| ["Chạy WanGP", "Khởi động công cụ sinh video WanGP và lấy link Gradio công khai của nó (dạng https://xxxx.gradio.live/)."], | |
| ["Dán link & Kết nối", "Dán link vào ô phía trên rồi bấm “🔌 Kết nối”. Trạng thái sẽ chuyển sang “Đã kết nối”."], | |
| ["Tạo video", "Giao diện WanGP gốc hiện ngay trong trang — nhập prompt và tạo video như bình thường."], | |
| ["Ngắt kết nối", "Xong việc bấm “🔌 Ngắt kết nối”. Link được ghi nhớ để lần sau vào lại nhanh."], | |
| ].map(([t, d], i) => ( | |
| <li key={i} style={{ display: "flex", gap: 12, alignItems: "flex-start" }}> | |
| <span style={{ | |
| flex: "0 0 24px", width: 24, height: 24, borderRadius: "50%", | |
| background: "var(--orange)", color: "#fff", fontSize: 12, fontWeight: 700, | |
| display: "grid", placeItems: "center", marginTop: 1, | |
| }}>{i + 1}</span> | |
| <span style={{ fontSize: 13, lineHeight: 1.5 }}> | |
| <b>{t}.</b> <span style={{ color: "var(--ink-2)" }}>{d}</span> | |
| </span> | |
| </li> | |
| ))} | |
| </ol> | |
| </div> | |
| </div> | |
| )} | |
| </> | |
| ); | |
| } | |