import React, { useState, useEffect, useRef } from 'react'; import { useParams } from 'react-router-dom'; import '../App.css'; // Use relative URLs in production, full URLs in development const isDevelopment = window.location.hostname === 'localhost'; const API_URL = isDevelopment ? 'http://localhost:8000' : ''; interface Message { id: number; text: string; agent: string; } interface Podcast { id: number; title: string; description: string; audio_file: string; filename?: string; category?: string; } interface PodcastContext { topic: string; believer_chunks: string[]; skeptic_chunks: string[]; } const PodcastForm: React.FC = () => { const { id } = useParams<{ id: string }>(); const [podcast, setPodcast] = useState<Podcast | null>(null); const [podcastContext, setPodcastContext] = useState<PodcastContext | null>(null); const [messages, setMessages] = useState<Message[]>([]); const [inputMessage, setInputMessage] = useState(''); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState<string>(""); const messagesEndRef = useRef<HTMLDivElement>(null); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); useEffect(() => { const fetchPodcastAndContext = async () => { try { if (!id) { setError("No podcast ID provided"); return; } // Fetch podcast details const response = await fetch(`${API_URL}/api/audio-list`); if (!response.ok) { throw new Error('Failed to fetch podcasts'); } const files = await response.json(); const podcastList = files.map((file: any, index: number) => { const filename = file.filename; const parts = filename.split('-'); let queryPart = '', descriptionPart = '', categoryWithExt = ''; if (parts.length >= 3) { [queryPart, descriptionPart, categoryWithExt] = parts; } else { queryPart = parts[0] || ''; descriptionPart = parts[1] || queryPart; categoryWithExt = parts[2] || 'general.mp3'; } const category = categoryWithExt.replace('.mp3', ''); return { id: index + 1, // Use 1-based index for consistency title: descriptionPart.replace(/_/g, ' ').replace(/^\w/, c => c.toUpperCase()), description: `A debate exploring ${queryPart.replace(/_/g, ' ')}`, audio_file: `${API_URL}${file.path}`, filename: filename, category: category.replace(/_/g, ' ') }; }); const selectedPodcast = podcastList.find(p => p.id === parseInt(id)); if (!selectedPodcast) { throw new Error(`Podcast with ID ${id} not found`); } setPodcast(selectedPodcast); // Fetch podcast context const contextResponse = await fetch(`${API_URL}/api/podcast/${id}/context`); if (contextResponse.ok) { const contextData: PodcastContext = await contextResponse.json(); setPodcastContext(contextData); } else { console.warn(`Could not fetch context for podcast ${id}`); } } catch (err) { console.error('Error fetching podcast:', err); setError(err instanceof Error ? err.message : 'Failed to fetch podcast'); } }; fetchPodcastAndContext(); }, [id]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!inputMessage.trim() || !id) return; const userMessage: Message = { id: messages.length + 1, text: inputMessage, agent: "user" }; setMessages(prev => [...prev, userMessage]); setInputMessage(''); setIsLoading(true); try { const response = await fetch(`${API_URL}/api/podcast-chat/${id}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ message: inputMessage }) }); if (!response.ok) { const errorData = await response.text(); throw new Error(`Server error: ${response.status} ${errorData}`); } const data = await response.json(); const botMessage: Message = { id: messages.length + 2, text: data.response, agent: "assistant" }; setMessages(prev => [...prev, botMessage]); } catch (error) { console.error('Error sending message:', error); setMessages(prev => [...prev, { id: prev.length + 1, text: `Error: ${error instanceof Error ? error.message : 'Failed to send message'}`, agent: "system" }]); } finally { setIsLoading(false); } }; return ( <div className="podcast-form-container"> <div className="chat-column"> <div className="podcast-chat-container"> {error && ( <div className="error-message">Error: {error}</div> )} {podcast && ( <div className="podcast-player-header"> <h2 className="podcast-title">{podcast.title}</h2> {podcast.category && ( <div className="category-pill">{podcast.category}</div> )} <p className="description">{podcast.description}</p> {podcastContext && ( <p className="podcast-topic">Topic: {podcastContext.topic}</p> )} <div className="audio-player"> <audio controls src={podcast.audio_file} > Your browser does not support the audio element. </audio> </div> </div> )} <div className="podcast-chat-messages"> {messages.map((message) => ( <div key={message.id} className={`message ${message.agent}-message`}> <div className="message-content"> <div className="agent-icon"> {message.agent === "user" ? "👤" : message.agent === "system" ? "🤖" : message.agent === "assistant" ? "🤖" : "💬"} </div> <div className="message-text-content"> <div className="agent-name">{message.agent}</div> <div className="message-text">{message.text}</div> </div> </div> </div> ))} {isLoading && ( <div className="message system-message"> <div className="message-content"> <div className="loading-dots">Processing</div> </div> </div> )} <div ref={messagesEndRef} /> </div> <form onSubmit={handleSubmit} className="podcast-chat-input-form"> <input type="text" value={inputMessage} onChange={(e) => setInputMessage(e.target.value)} placeholder="Ask a question about this podcast..." className="podcast-chat-input" disabled={isLoading} /> <button type="submit" className="podcast-chat-send-button" disabled={isLoading}> Send </button> </form> {podcastContext && ( <div className="relevant-chunks"> <div className="chunk-section"> <h4>Key Points</h4> <ul> {podcastContext.believer_chunks.map((chunk, i) => ( <li key={`believer-${i}`}>{chunk}</li> ))} {podcastContext.skeptic_chunks.map((chunk, i) => ( <li key={`skeptic-${i}`}>{chunk}</li> ))} </ul> </div> </div> )} </div> </div> </div> ); }; export default PodcastForm;