midterm / podcraft /src /pages /PodcastForm.tsx
Nagesh Muralidhar
midterm-submission
4972b4c
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;