import React, { useEffect, useRef, useState } from 'react'; import { useParams, Link } from 'react-router-dom'; import { Layout, Space, Button, App, Empty, Input, Tabs } from 'antd'; import { LockOutlined, EyeOutlined, CopyOutlined, ShareAltOutlined, HomeOutlined, FileTextOutlined, PartitionOutlined, AudioOutlined } from '@ant-design/icons'; import apiClient from '../utils/apiClient'; import { buildApiUrl, API_ENDPOINTS } from '../config/api'; import MarkdownRenderer from '../components/MarkdownRenderer'; import MindMap from '../components/MindMap'; import AudioPlayerBar from '../components/AudioPlayerBar'; import TranscriptTimeline from '../components/TranscriptTimeline'; import tools from '../utils/tools'; import configService, { DEFAULT_BRANDING_CONFIG } from '../utils/configService'; import './MeetingPreview.css'; const { Content } = Layout; const MeetingPreview = () => { const { meeting_id } = useParams(); const { message } = App.useApp(); const audioRef = useRef(null); const transcriptRefs = useRef([]); const [meeting, setMeeting] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [password, setPassword] = useState(''); const [passwordError, setPasswordError] = useState(''); const [passwordRequired, setPasswordRequired] = useState(false); const [isAuthorized, setIsAuthorized] = useState(false); const [branding, setBranding] = useState(DEFAULT_BRANDING_CONFIG); const [transcript, setTranscript] = useState([]); const [audioUrl, setAudioUrl] = useState(''); const [activeSegmentIndex, setActiveSegmentIndex] = useState(-1); const [playbackRate, setPlaybackRate] = useState(1); useEffect(() => { configService.getBrandingConfig().then(setBranding).catch(() => {}); }, []); useEffect(() => { setMeeting(null); setTranscript([]); transcriptRefs.current = []; setAudioUrl(''); setActiveSegmentIndex(-1); setPlaybackRate(1); setError(null); setPassword(''); setPasswordError(''); setPasswordRequired(false); setIsAuthorized(false); fetchPreview(); }, [meeting_id]); const fetchTranscriptAndAudio = async () => { const [transcriptRes, audioRes] = await Promise.allSettled([ apiClient.get(buildApiUrl(API_ENDPOINTS.MEETINGS.TRANSCRIPT(meeting_id))), apiClient.get(buildApiUrl(API_ENDPOINTS.MEETINGS.AUDIO(meeting_id))), ]); if (transcriptRes.status === 'fulfilled') { setTranscript(Array.isArray(transcriptRes.value.data) ? transcriptRes.value.data : []); } else { setTranscript([]); } if (audioRes.status === 'fulfilled') { setAudioUrl(buildApiUrl(`${API_ENDPOINTS.MEETINGS.AUDIO(meeting_id)}/stream`)); } else { setAudioUrl(''); } }; const fetchPreview = async (pwd = '') => { setLoading(true); try { const endpoint = API_ENDPOINTS.MEETINGS.PREVIEW_DATA(meeting_id); const url = buildApiUrl(`${endpoint}${pwd ? `?password=${encodeURIComponent(pwd)}` : ''}`); const res = await apiClient.get(url); setMeeting(res.data); setIsAuthorized(true); setPasswordRequired(false); setPasswordError(''); setError(null); await fetchTranscriptAndAudio(); } catch (err) { const responseCode = String(err?.response?.data?.code || ''); if (responseCode === '401') { setMeeting(null); setIsAuthorized(false); setPasswordRequired(true); setPasswordError(err?.response?.data?.message || ''); setError(null); } else { setMeeting(null); setIsAuthorized(false); setPasswordRequired(false); setPasswordError(''); setError(err?.response?.data?.message || '无法加载会议预览'); } } finally { setLoading(false); } }; const handleCopyLink = async () => { await navigator.clipboard.writeText(window.location.href); message.success('分享链接已复制'); }; const handleShare = async () => { if (navigator.share) { try { await navigator.share({ title: meeting?.title || branding.preview_title, url: window.location.href }); return; } catch { // Fallback to copying the link when native share is cancelled or unavailable. } } await handleCopyLink(); }; const handleTimeUpdate = () => { if (!audioRef.current || !transcript.length) { return; } const currentMs = audioRef.current.currentTime * 1000; const index = transcript.findIndex( (item) => currentMs >= item.start_time_ms && currentMs <= item.end_time_ms, ); setActiveSegmentIndex(index); }; const jumpToSegment = (segment) => { if (!audioRef.current || !segment) { return; } audioRef.current.currentTime = (segment.start_time_ms || 0) / 1000; audioRef.current.play().catch(() => {}); }; useEffect(() => { if (activeSegmentIndex < 0) { return; } transcriptRefs.current[activeSegmentIndex]?.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, [activeSegmentIndex]); const handleVerify = () => { if (!password) { message.warning('请输入访问密码'); return; } fetchPreview(password); }; if (loading && !meeting) { return (
正在加载会议预览...
{error}
请输入访问密码以查看会议纪要