/** * Панель персон для Timeline * Показывает всех обнаруженных персон и их статистику */ import { Eye, EyeOff, Filter, Search, Settings, Users } 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 { Input } from "@/components/ui/input" import { ScrollArea } from "@/components/ui/scroll-area" import { Slider } from "@/components/ui/slider" import { Switch } from "@/components/ui/switch" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" import { useTimelinePersons } from "../../hooks/use-timeline-persons" interface PersonsPanelProps { className?: string } export function PersonsPanel({ className }: PersonsPanelProps) { const { persons, state, analyzeTimelineForPersons, clearPersonsAnalysis, showPersonDetail, enablePersonDetection, setEnablePersonDetection, confidenceThreshold, setConfidenceThreshold, } = useTimelinePersons() const [searchQuery, setSearchQuery] = useState("") const [selectedTags, setSelectedTags] = useState([]) const [showSettings, setShowSettings] = useState(false) // Фильтрация персон const filteredPersons = persons.filter((person) => { const matchesSearch = (person.name || "").toLowerCase().includes(searchQuery.toLowerCase()) || (person.notes || "").toLowerCase().includes(searchQuery.toLowerCase()) const matchesTags = selectedTags.length === 0 || selectedTags.some((tag) => person.tags?.includes(tag)) return matchesSearch && matchesTags }) // Получение всех тегов const allTags = Array.from(new Set(persons.flatMap((p) => p.tags || []))) // Статистика const totalAppearances = state.appearances.length const avgConfidence = state.appearances.length > 0 ? Math.round((state.appearances.reduce((sum, app) => sum + app.confidence, 0) / state.appearances.length) * 100) : 0 return (
Персоны ({persons.length})
Настройки обнаружения персон Анализировать Timeline Очистить анализ
{/* Настройки */} {showSettings && (
Автообнаружение
Уверенность {Math.round(confidenceThreshold * 100)}%
setConfidenceThreshold(value)} min={0.3} max={1.0} step={0.05} className="w-full" />
)} {/* Статистика */} {state.appearances.length > 0 && (
Появлений: {totalAppearances} Средняя уверенность: {avgConfidence}%
)} {/* Прогресс анализа */} {state.isAnalyzing && (
Анализ... {state.analysisProgress}%
)} {/* Поиск */}
setSearchQuery(e.target.value)} className="pl-7 h-7 text-xs" />
{/* Фильтр по тегам */} {allTags.length > 0 && (
Теги:
{allTags.map((tag) => ( { setSelectedTags((prev) => (prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag])) }} > {tag} ))}
)} {/* Ошибка */} {state.error &&
{state.error}
} {filteredPersons.length === 0 ? (
{persons.length === 0 ? "Персоны не обнаружены. Запустите анализ Timeline." : "Персоны не найдены по заданным критериям."}
) : (
{filteredPersons.map((person) => { const personAppearances = state.appearances.filter((app) => app.personId === person.id) const avgConfidence = personAppearances.length > 0 ? Math.round( (personAppearances.reduce((sum, app) => sum + app.confidence, 0) / personAppearances.length) * 100, ) : 0 return (
showPersonDetail(person.id)} > {/* Аватар */}
{person.thumbnails && person.thumbnails.length > 0 ? ( {person.name ) : ( )}
{/* Информация */}
{person.name || "Безымянная персона"}
{personAppearances.length} появлений {avgConfidence > 0 && {avgConfidence}%}
{person.tags && person.tags.length > 0 && (
{person.tags.slice(0, 2).map((tag) => ( {tag} ))} {person.tags.length > 2 && ( +{person.tags.length - 2} )}
)}
{/* Индикатор уверенности */} {avgConfidence > 0 && (
= 80 ? "bg-green-500" : avgConfidence >= 60 ? "bg-yellow-500" : "bg-red-500"} `} /> )}
) })}
)} ) }