"use client"; import { useState, useEffect } from "react"; // 动态获取 API 地址:服务端使用 localhost,客户端使用当前域名 const API_BASE = typeof window !== 'undefined' ? `http://${window.location.hostname}:8006` : 'http://localhost:8006'; // 类型定义 interface Material { id: string; name: string; scene: string; size_mb: number; path: string; } interface Task { task_id: string; status: string; progress: number; message: string; download_url?: string; } export default function Home() { const [materials, setMaterials] = useState([]); const [selectedMaterial, setSelectedMaterial] = useState(""); const [text, setText] = useState( "大家好,欢迎来到我的频道,今天给大家分享一些有趣的内容。" ); const [voice, setVoice] = useState("zh-CN-YunxiNeural"); const [isGenerating, setIsGenerating] = useState(false); const [currentTask, setCurrentTask] = useState(null); const [generatedVideo, setGeneratedVideo] = useState(null); const [fetchError, setFetchError] = useState(null); const [debugData, setDebugData] = useState(""); const [isUploading, setIsUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [uploadError, setUploadError] = useState(null); // 可选音色 const voices = [ { id: "zh-CN-YunxiNeural", name: "云溪 (男声-年轻)" }, { id: "zh-CN-YunjianNeural", name: "云健 (男声-新闻)" }, { id: "zh-CN-YunyangNeural", name: "云扬 (男声-专业)" }, { id: "zh-CN-XiaoxiaoNeural", name: "晓晓 (女声-活泼)" }, { id: "zh-CN-XiaoyiNeural", name: "晓伊 (女声-温柔)" }, ]; // 加载素材列表 useEffect(() => { fetchMaterials(); }, []); const fetchMaterials = async () => { try { setFetchError(null); setDebugData("Loading..."); // Add timestamp to prevent caching const url = `${API_BASE}/api/materials/?t=${new Date().getTime()}`; const res = await fetch(url); if (!res.ok) { throw new Error(`HTTP ${res.status} ${res.statusText}`); } const text = await res.text(); // Get raw text first setDebugData(text.substring(0, 200) + (text.length > 200 ? "..." : "")); // Show preview const data = JSON.parse(text); setMaterials(data.materials || []); if (data.materials?.length > 0) { if (!selectedMaterial) { setSelectedMaterial(data.materials[0].id); } } } catch (error) { console.error("获取素材失败:", error); setFetchError(String(error)); setDebugData(`Error: ${String(error)}`); } }; // 上传视频 const handleUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; // 验证文件类型 const validTypes = ['.mp4', '.mov', '.avi']; const ext = file.name.toLowerCase().slice(file.name.lastIndexOf('.')); if (!validTypes.includes(ext)) { setUploadError('仅支持 MP4、MOV、AVI 格式'); return; } setIsUploading(true); setUploadProgress(0); setUploadError(null); const formData = new FormData(); formData.append('file', file); // 使用 XMLHttpRequest 以获取上传进度 const xhr = new XMLHttpRequest(); xhr.upload.onprogress = (event) => { if (event.lengthComputable) { const progress = Math.round((event.loaded / event.total) * 100); setUploadProgress(progress); } }; xhr.onload = () => { setIsUploading(false); if (xhr.status >= 200 && xhr.status < 300) { fetchMaterials(); // 刷新素材列表 setUploadProgress(100); } else { setUploadError(`上传失败: ${xhr.statusText}`); } }; xhr.onerror = () => { setIsUploading(false); setUploadError('网络错误,上传失败'); }; xhr.open('POST', `${API_BASE}/api/materials/`); xhr.send(formData); // 清空 input 以便可以再次选择同一文件 e.target.value = ''; }; // 生成视频 const handleGenerate = async () => { if (!selectedMaterial || !text.trim()) { alert("请选择素材并输入文案"); return; } setIsGenerating(true); setGeneratedVideo(null); try { // 查找选中的素材对象以获取路径 const materialObj = materials.find(m => m.id === selectedMaterial); if (!materialObj) { alert("素材数据异常"); return; } // 创建生成任务 const res = await fetch(`${API_BASE}/api/videos/generate`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ material_path: materialObj.path, text: text, voice: voice, add_subtitle: true, }), }); const data = await res.json(); const taskId = data.task_id; // 轮询任务状态 const pollTask = async () => { const taskRes = await fetch(`${API_BASE}/api/videos/tasks/${taskId}`); const taskData: Task = await taskRes.json(); setCurrentTask(taskData); if (taskData.status === "completed") { setGeneratedVideo(`${API_BASE}${taskData.download_url}`); setIsGenerating(false); } else if (taskData.status === "failed") { alert("视频生成失败: " + taskData.message); setIsGenerating(false); } else { setTimeout(pollTask, 1000); } }; pollTask(); } catch (error) { console.error("生成失败:", error); setIsGenerating(false); } }; return (
{/* Header */}

🎬 ViGent

{/* 左侧: 输入区域 */}
{/* 素材选择 */}

📹 选择素材视频

{/* 隐藏的文件输入 */}
{/* 上传进度条 */} {isUploading && (
📤 上传中... {uploadProgress}%
)} {/* 上传错误提示 */} {uploadError && (
❌ {uploadError}
)} {fetchError ? (
获取素材失败: {fetchError}
API: {API_BASE}/api/materials/
) : materials.length === 0 ? (
📁

暂无素材视频

点击上方「📤 上传视频」按钮添加素材

) : (
{materials.map((m) => ( ))}
)}
{/* 文案输入 */}

✍️ 输入口播文案