/** * Панель управления связанными клипами */ import { AlertTriangle, Eye, EyeOff, Info, Link2, RefreshCw, Search, Target, Unlink } from "lucide-react" import { useState } from "react" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog" import { ScrollArea } from "@/components/ui/scroll-area" import { Separator } from "@/components/ui/separator" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" import { cn } from "@/lib/utils" import { useLinkedClips } from "../hooks/use-linked-clips" interface LinkedClipsPanelProps { className?: string compact?: boolean } export function LinkedClipsPanel({ className, compact = false }: LinkedClipsPanelProps) { const { linkedPairs, linkedCount, hasActiveLinks, unlinkClips, unlinkClip, findPotentialLinks, autoLinkClipsByMedia, validateLinkSync, syncLinkedClips, getLinkStats, } = useLinkedClips() const [showInactive, setShowInactive] = useState(false) const [potentialLinks, setPotentialLinks] = useState([]) const [isSearching, setIsSearching] = useState(false) const stats = getLinkStats() const handleFindPotentialLinks = async () => { setIsSearching(true) // Имитируем асинхронную операцию await new Promise((resolve) => setTimeout(resolve, 500)) const links = findPotentialLinks() setPotentialLinks(links) setIsSearching(false) } const handleAutoLink = (mediaFileId: string) => { const linkedCount = autoLinkClipsByMedia(mediaFileId) // Здесь можно добавить уведомление о результате console.log(`Auto-linked ${linkedCount} clips`) } const visiblePairs = showInactive ? linkedPairs : linkedPairs.filter((pair) => pair.isActive) const getLinkTypeColor = (type: string) => { switch (type) { case "video-audio": return "bg-blue-500" case "audio-video": return "bg-green-500" case "multi-camera": return "bg-purple-500" default: return "bg-gray-500" } } const getLinkTypeLabel = (type: string) => { switch (type) { case "video-audio": return "Video → Audio" case "audio-video": return "Audio → Video" case "multi-camera": return "Multi-Camera" default: return type } } if (compact) { return (
Linked Clips
{stats.totalLinks} total, {stats.brokenLinks} broken
{hasActiveLinks && ( <> Active )}
) } return (
Linked Clips
{linkedCount} links Link Statistics
{stats.totalLinks}
Total Links
{stats.brokenLinks}
Broken Links
Video → Audio {stats.videoAudioLinks}
Audio → Video {stats.audioVideoLinks}
Multi-Camera {stats.multiCameraLinks}
{visiblePairs.length === 0 ? (
No linked clips found
) : ( visiblePairs.map((pair) => { const isValid = validateLinkSync(pair.clip1.id) return (
{getLinkTypeLabel(pair.type)} {!isValid && }
Sync clips Unlink clips
{pair.clip1.name} {pair.clip1.startTime.toFixed(2)}s
{pair.clip2.name} {pair.clip2.startTime.toFixed(2)}s
) }) )}
{/* Потенциальные связи */} {potentialLinks.length > 0 && (

Potential Links

{potentialLinks.slice(0, 5).map((link, index) => (
{link.clip1.name} ↔ {link.clip2.name}
{link.reason} ({link.confidence}% confidence)
))}
)} ) } export default LinkedClipsPanel