import { useEffect, useState } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { ArrowLeft, Download, Calendar, Clock, Users, Search, FileText, AlignLeft, Loader2, User, Sparkles, Edit2, Check, X, ChevronDown, Copy, CheckCheck } from 'lucide-react' import { useAppStore } from '../store/useAppStore' import { getMeeting, downloadMarkdown, formatDuration, formatTimestamp, generateSummary, updateMeetingTitle } from '../api/meeting' import Button from '../components/Button' export default function MeetingDetail() { const { id } = useParams() const navigate = useNavigate() const { currentMeeting, loading, setCurrentMeeting, refreshMeetings } = useAppStore() const [search, setSearch] = useState('') const [summarizing, setSummarizing] = useState(false) const [editingTitle, setEditingTitle] = useState(false) const [titleInput, setTitleInput] = useState('') const [summaryCollapsed, setSummaryCollapsed] = useState(false) const [briefCopied, setBriefCopied] = useState(false) const [detailedCopied, setDetailedCopied] = useState(false) useEffect(() => { loadMeeting() }, [id]) const loadMeeting = async () => { const data = await getMeeting(id) setCurrentMeeting(data) } const handleGenerateSummary = async () => { setSummarizing(true) try { const updated = await generateSummary(id) setCurrentMeeting(updated) } catch (err) { alert('生成摘要失败: ' + err.message) } finally { setSummarizing(false) } } const handleEditTitle = () => { setTitleInput(currentMeeting?.title || '') setEditingTitle(true) } const handleSaveTitle = async () => { if (!titleInput.trim()) return try { const updated = await updateMeetingTitle(id, titleInput.trim()) setCurrentMeeting(updated) refreshMeetings() } catch (err) { alert('保存失败: ' + err.message) } setEditingTitle(false) } const handleCancelTitle = () => { setEditingTitle(false) setTitleInput('') } const copyToClipboard = async (text, type) => { await navigator.clipboard.writeText(text) if (type === 'brief') { setBriefCopied(true) setTimeout(() => setBriefCopied(false), 2000) } else { setDetailedCopied(true) setTimeout(() => setDetailedCopied(false), 2000) } } if (loading || !currentMeeting) { return (
) } const meeting = currentMeeting const segments = meeting.segments || [] const filteredSegments = search ? segments.filter(s => s.text.toLowerCase().includes(search.toLowerCase())) : segments return (
{/* 返回按钮 */} {/* 详情头部 */}
{editingTitle ? (
setTitleInput(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSaveTitle()} className="text-lg font-semibold text-primary glass-input rounded-lg px-3 py-1 focus:outline-none w-full max-w-sm" autoFocus />
) : (

{meeting.title}

)}
{meeting.date?.slice(0, 10) || '未知'} {formatDuration(meeting.duration)} {meeting.speaker_count || 0} 人
{meeting.status === 'completed' && (
)}
{/* 音频播放器 */} {meeting.audio_path && meeting.status === 'completed' && (
)}
{/* 处理中状态 */} {meeting.status === 'processing' && (

正在转录中...

请稍候,预计需要几分钟时间

)} {/* 摘要区域 */} {(meeting.brief_summary || meeting.detailed_summary) && (
{!summaryCollapsed && (
{meeting.brief_summary && (

简要摘要

{meeting.brief_summary}

)} {meeting.detailed_summary && (

详细纪要

{meeting.detailed_summary}

)}
)}
)} {/* 转写内容 */} {meeting.status === 'completed' && (

转写内容

setSearch(e.target.value)} className="pl-8 pr-3 py-1.5 glass-input rounded-md text-xs text-primary placeholder-muted focus:outline-none w-40" />

{filteredSegments.length} / {segments.length} 条

{filteredSegments.map((seg, i) => (
说话人 {seg.speaker} {formatTimestamp(seg.start)}

{seg.text}

))}
)}
) }