"use client"; import React, { useState, useRef } from "react"; import { Video, Loader2, AlertCircle, MoreVertical, Download, Trash2, Play, Pause, Volume2, VolumeX } from "lucide-react"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Slider } from "@/components/ui/slider"; import { AgentExecutionSnapshotDialog } from "@/components/agents/AgentExecutionSnapshotDialog"; import { toast } from "sonner"; import { useCanvasStore } from "@/store/canvas"; import type { CanvasNode, VideoNodeData } from "@/types/canvas"; import { cn } from "@/lib/utils"; interface VideoNodeProps { node: CanvasNode; isSelected: boolean; } export function VideoNode({ node, isSelected }: VideoNodeProps) { const data = node.data as VideoNodeData; const agentExecutionSnapshotId = typeof data.metadata?.agentExecutionSnapshotId === "string" ? data.metadata.agentExecutionSnapshotId.trim() : ""; const shortAgentExecutionSnapshotId = agentExecutionSnapshotId ? agentExecutionSnapshotId.slice(0, 8) : ""; const videoRef = useRef(null); const [isHovered, setIsHovered] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [isMuted, setIsMuted] = useState(true); const [progress, setProgress] = useState(0); const [snapshotDialogOpen, setSnapshotDialogOpen] = useState(false); const updateNode = useCanvasStore((state) => state.updateNode); const deleteNode = useCanvasStore((state) => state.deleteNode); // 处理视频上传 - 上传到服务器获取 URL const handleVideoUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; // 检查文件大小(限制 100MB) if (file.size > 100 * 1024 * 1024) { toast.error("视频文件不能超过 100MB"); return; } // 更新状态为上传中 updateNode(node.id, { data: { ...data, status: 'generating' }, }); try { // 上传视频到服务器 const formData = new FormData(); formData.append('file', file); const response = await fetch('/api/video/upload', { method: 'POST', body: formData, }); if (!response.ok) { throw new Error('上传失败'); } const result = await response.json(); // 使用服务器返回的 URL updateNode(node.id, { data: { ...data, src: result.url, status: 'completed' }, }); toast.success('视频上传成功'); } catch (error) { console.error('视频上传失败:', error); toast.error('视频上传失败,请重试'); updateNode(node.id, { data: { ...data, status: 'failed' }, }); } }; // 播放/暂停 const togglePlay = () => { if (videoRef.current) { if (isPlaying) { videoRef.current.pause(); } else { videoRef.current.play(); } setIsPlaying(!isPlaying); } }; // 静音/取消静音 const toggleMute = () => { if (videoRef.current) { videoRef.current.muted = !isMuted; setIsMuted(!isMuted); } }; // 更新进度 const handleTimeUpdate = () => { if (videoRef.current) { const progress = (videoRef.current.currentTime / videoRef.current.duration) * 100; setProgress(progress); } }; // 跳转进度 const handleSeek = (value: number[]) => { if (videoRef.current) { const time = (value[0] / 100) * videoRef.current.duration; videoRef.current.currentTime = time; setProgress(value[0]); } }; // 下载视频 const handleDownload = () => { if (data.src) { const link = document.createElement('a'); link.href = data.src; link.download = `video-${node.id}.mp4`; link.click(); } }; return (
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > {/* 视频内容 */} {data.src ? (
) : data.status === 'generating' ? (
生成中... {data.progress && ( {data.progress}% )}
) : data.status === 'failed' ? (
生成失败
) : ( )} {/* 悬浮操作栏 */} {isHovered && data.src && (
下载 deleteNode(node.id)} > 删除
)} {shortAgentExecutionSnapshotId ? ( ) : null} {/* 提示词显示 */} {data.prompt && isHovered && (

{data.prompt}

)}
); }