website_content / frontend /src /pages /CreateVideo.jsx
Administrator
Claude Opus 5
fix(api): 7 màn còn lại dùng chung lớp đọc JSON an toàn
d94104e
Raw History Blame Contribute Delete
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>
)}
</>
);
}