import { useState, useEffect, useRef } from 'react' import { ArrowLeft, Play, Pause, Volume2, Clock, Download, Share2, Calendar, Globe, Lock } from 'lucide-react' import { api } from '../api' export default function PostDetail({ postId, user, onBack }) { const [post, setPost] = useState(null) const [metadata, setMetadata] = useState(null) const [chunks, setChunks] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) // Audio player state const [isPlaying, setIsPlaying] = useState(false) const [currentTime, setCurrentTime] = useState(0) const [duration, setDuration] = useState(0) const [volume, setVolume] = useState(1) const [downloading, setDownloading] = useState(false) const audioRef = useRef(null) const [audioSrc, setAudioSrc] = useState(null) useEffect(() => { if (postId) { loadPostData() } }, [postId]) const loadPostData = async () => { setLoading(true) setError(null) try { // Load post data const postData = await api.getPost(postId) setPost(postData) // Load audio URL if available if (postData.audio_url && audioRef.current) { setPost(postData) setAudioSrc(postData.audio_url) audioRef.current.src = postData.audio_url audioRef.current.load() } // Load metadata (contains transcript) try { const metadataResponse = await api.getPostMetadata(postId) if (metadataResponse && metadataResponse.metadata) { const metadataObj = JSON.parse(metadataResponse.metadata) setMetadata(metadataObj) } } catch (err) { console.error('Failed to load metadata:', err) } // Load RAG chunks (timestamped transcript segments) try { const chunksResponse = await api.request(`/posts/${postId}/chunks?limit=1000`) setChunks(chunksResponse.chunks || []) } catch (err) { console.error('Failed to load chunks:', err) } } catch (err) { setError(err.message || 'Failed to load post') } finally { setLoading(false) } } const formatDate = (dateString) => { return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' }) } const formatTime = (seconds) => { if (!seconds || isNaN(seconds)) return '0:00' const mins = Math.floor(seconds / 60) const secs = Math.floor(seconds % 60) return `${mins}:${secs.toString().padStart(2, '0')}` } const extractTranscript = () => { if (!metadata?.prompt) return null const match = metadata.prompt.match(/Transcript:\n([\s\S]*?)\n\nAnswer user questions/) return match ? match[1].trim() : null } // Audio player handlers const togglePlay = () => { if (!audioRef.current) return if (isPlaying) { audioRef.current.pause() } else { audioRef.current.play() } setIsPlaying(!isPlaying) } const handleTimeUpdate = () => { if (audioRef.current) { setCurrentTime(audioRef.current.currentTime) } } const handleLoadedMetadata = () => { if (audioRef.current) { setDuration(audioRef.current.duration) } } const handleSeek = (e) => { const rect = e.currentTarget.getBoundingClientRect() const x = e.clientX - rect.left const percentage = x / rect.width const newTime = percentage * duration if (audioRef.current) { audioRef.current.currentTime = newTime setCurrentTime(newTime) } } const handleVolumeChange = (e) => { const newVolume = parseFloat(e.target.value) setVolume(newVolume) if (audioRef.current) { audioRef.current.volume = newVolume } } const handleEnded = () => { setIsPlaying(false) setCurrentTime(0) } const handleDownload = async () => { if (downloading) return setDownloading(true) try { const zipBlob = await api.exportPost(postId) const url = window.URL.createObjectURL(zipBlob) const a = document.createElement("a") a.href = url a.download = `${post.title.replace(/[^a-zA-Z0-9]/g, "_")}_${postId}.zip` document.body.appendChild(a) a.click() a.remove() window.URL.revokeObjectURL(url) } catch (err) { console.error("Failed to download:", err) alert(`Download failed: ${err.message}`) } finally { setDownloading(false) } } const handleShare = () => { if (navigator.share) { navigator.share({ title: post.title, text: post.description, url: window.location.href }) } else { // Copy link to clipboard navigator.clipboard.writeText(window.location.href) alert('Link copied to clipboard!') } } const progress = duration > 0 ? (currentTime / duration) * 100 : 0 const transcript = extractTranscript() if (loading) { return (
) } if (error || !post) { return (

{error || 'Post not found'}

) } return (
{/* Back Button */} {/* Header Card */}
{/* Title and Meta */}

{post.title}

{post.visibility === 'private' ? ( ) : ( )}
{/* Metadata */}
{formatDate(post.created_at)}
{post.status}
{post.language && ( <> {post.language.toUpperCase()} )}
{/* Description */} {post.description && (

{post.description}

)} {/* Action Buttons */}
{/* Audio Player */} {post.status === 'ready' && (

Audio Player

)} {/* Full Transcript */} {transcript && (

Full Transcript

{transcript}

{metadata?.transcript_length_chars && (

{metadata.transcript_length_chars.toLocaleString()} characters

)}
)} {/* Timestamped Segments */} {chunks.length > 0 && (

Timestamped Segments ({chunks.length})

{chunks.map((chunk, index) => (
{formatTime(chunk.start_sec)}

{chunk.text}

{chunk.confidence && (
Confidence: {Math.round(chunk.confidence * 100)}%
)}
))}
)} {/* Metadata */} {metadata && (

Metadata

{metadata.source_file && ( <>
Source File:
{metadata.source_file}
)} {metadata.language && ( <>
Language:
{metadata.language.toUpperCase()}
)} {metadata.transcript_length_chars && ( <>
Transcript Length:
{metadata.transcript_length_chars.toLocaleString()} characters
)} {chunks.length > 0 && ( <>
Segments:
{chunks.length} chunks
)}
)}
) }