Files
ai-podcast/src/app/page.tsx
Rosario Moscato f31780cbf5 feat: integrate shadcn components and enable dark mode by default
- Add shadcn/ui components (Button, Input, Card, Progress)
- Replace custom UI elements with shadcn components throughout the app
- Install required dependencies (@radix-ui, lucide-react, class-variance-authority)
- Create utils file for proper component styling
- Enable dark mode as default theme
- Update CSS with proper oklch color system and dark mode variables
- Remove media query for automatic color scheme detection
- Maintain all existing functionality with improved component consistency

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-21 20:08:43 +02:00

318 lines
12 KiB
TypeScript

'use client';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';
import { Play, Pause, RotateCcw, Volume2 } from 'lucide-react';
interface Message {
id: string;
speaker: 'host1' | 'host2';
text: string;
timestamp: string;
}
export default function Home() {
const [url, setUrl] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [messages, setMessages] = useState<Message[]>([]);
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
const [progressInterval, setProgressInterval] = useState<NodeJS.Timeout | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
// Simulate API call
setTimeout(() => {
setMessages(mockMessages);
setIsLoading(false);
setDuration(320); // 5 minutes 20 seconds
}, 2000);
};
const togglePlay = () => {
if (isPlaying) {
setIsPlaying(false);
if (progressInterval) {
clearInterval(progressInterval);
setProgressInterval(null);
}
} else {
setIsPlaying(true);
const interval = setInterval(() => {
setCurrentTime(prev => {
if (prev >= duration) {
setIsPlaying(false);
clearInterval(interval);
setProgressInterval(null);
return duration;
}
return prev + 1;
});
}, 1000);
setProgressInterval(interval);
}
};
const restartAudio = () => {
setCurrentTime(0);
setIsPlaying(false);
if (progressInterval) {
clearInterval(progressInterval);
setProgressInterval(null);
}
};
return (
<div className="min-h-screen bg-background">
{/* Header */}
<header className="border-b border-gray-200 dark:border-gray-800">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<h1 className="text-2xl font-bold text-foreground">AI Podcast Generator</h1>
</div>
</header>
{/* Main Content */}
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 sm:py-8">
<div className="grid grid-cols-1 xl:grid-cols-3 gap-4 lg:gap-6 h-[calc(100vh-10rem)] sm:h-[calc(100vh-12rem)]">
{/* Left Column - URL Input */}
<div className="xl:col-span-1">
<Card className="h-full">
<CardHeader>
<CardTitle>Generate Podcast</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="url" className="block text-sm font-medium mb-2">
Enter Website URL
</label>
<Input
type="url"
id="url"
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="https://example.com"
required
/>
</div>
<Button
type="submit"
className="w-full"
disabled={isLoading || !url}
>
{isLoading ? 'Generating...' : 'Generate Podcast'}
</Button>
</form>
{messages.length > 0 && (
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-md">
<p className="text-sm text-green-800 dark:text-green-200">
Podcast generated successfully! Listen to the conversation below.
</p>
</div>
)}
</CardContent>
</Card>
</div>
{/* Middle Column - Conversation */}
<div className="xl:col-span-1">
<Card className="h-full flex flex-col">
<CardHeader>
<CardTitle>Podcast Conversation</CardTitle>
</CardHeader>
<CardContent className="flex-1 overflow-y-auto space-y-4 p-6">
{messages.length === 0 ? (
<div className="text-center text-muted-foreground mt-8">
<p>Enter a URL and generate a podcast to see the conversation here.</p>
</div>
) : (
messages.map((message) => (
<div
key={message.id}
className={`flex ${message.speaker === 'host1' ? 'justify-start' : 'justify-end'}`}
>
<Card
className={`max-w-[80%] transition-all duration-200 hover:scale-[1.02] ${
message.speaker === 'host1'
? 'bg-blue-100 dark:bg-blue-900/30 hover:bg-blue-200 dark:hover:bg-blue-900/50'
: 'bg-muted hover:bg-muted/80'
}`}
>
<CardContent className="p-4">
<div className="font-medium text-sm mb-1">
{message.speaker === 'host1' ? 'Alex' : 'Sarah'}
</div>
<p className="text-sm">{message.text}</p>
<div className="text-xs text-muted-foreground mt-1">{message.timestamp}</div>
</CardContent>
</Card>
</div>
))
)}
</CardContent>
</Card>
</div>
{/* Right Column - Audio Player */}
<div className="xl:col-span-1">
<Card className="h-full">
<CardHeader>
<CardTitle>Audio Player</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
{messages.length === 0 ? (
<div className="text-center text-muted-foreground mt-8">
<p>Generate a podcast to enable audio playback.</p>
</div>
) : (
<div className="space-y-6">
{/* Audio Controls */}
<div className="flex justify-center items-center space-x-4">
<Button
onClick={restartAudio}
variant="outline"
size="icon"
className="rounded-full transition-all duration-200 hover:scale-110"
title="Restart"
>
<RotateCcw className="w-4 h-4" />
</Button>
<Button
onClick={togglePlay}
size="icon"
className="rounded-full transition-all duration-200 hover:scale-110 shadow-lg hover:shadow-xl"
title={isPlaying ? 'Pause' : 'Play'}
>
{isPlaying ? (
<Pause className="w-5 h-5" />
) : (
<Play className="w-5 h-5" />
)}
</Button>
</div>
{/* Progress Bar */}
<div className="space-y-2">
<Progress
value={(currentTime / duration) * 100}
className="w-full"
/>
<div className="flex justify-between text-sm text-muted-foreground">
<span>{formatTime(currentTime)}</span>
<span>{formatTime(duration)}</span>
</div>
</div>
{/* Volume Control */}
<div className="flex items-center space-x-2">
<Volume2 className="w-5 h-5 text-muted-foreground" />
<input
type="range"
min="0"
max="100"
defaultValue="70"
className="flex-1 h-2 bg-muted rounded-lg appearance-none cursor-pointer"
/>
</div>
{/* Episode Info */}
<Card>
<CardContent className="p-4">
<h3 className="font-medium mb-2">Episode Details</h3>
<p className="text-sm text-muted-foreground">
Duration: {formatTime(duration)}
</p>
<p className="text-sm text-muted-foreground">
Speakers: Alex & Sarah
</p>
</CardContent>
</Card>
</div>
)}
</CardContent>
</Card>
</div>
</div>
</main>
</div>
);
}
// Helper function to format time
function formatTime(seconds: number): string {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
// Mock conversation data
const mockMessages: Message[] = [
{
id: '1',
speaker: 'host1',
text: "Hey Sarah! Today we're discussing this fascinating article about artificial intelligence and its impact on creative industries.",
timestamp: '0:15'
},
{
id: '2',
speaker: 'host2',
text: "That's really interesting, Alex! I've been following this topic closely. The article mentions some really compelling points about AI-generated art and music.",
timestamp: '0:45'
},
{
id: '3',
speaker: 'host1',
text: "Exactly! What I found most surprising was the section about how AI is actually helping human creators become more productive rather than replacing them entirely.",
timestamp: '1:20'
},
{
id: '4',
speaker: 'host2',
text: "That's a crucial distinction. The article highlights several case studies where artists are using AI as a collaborative tool to enhance their creative process.",
timestamp: '1:55'
},
{
id: '5',
speaker: 'host1',
text: "I particularly loved the example about the musician who used AI to generate backing tracks and then added their own creative twist on top.",
timestamp: '2:30'
},
{
id: '6',
speaker: 'host2',
text: "And what about the ethical considerations? The article raises some important questions about copyright and attribution when AI is involved in the creative process.",
timestamp: '3:05'
},
{
id: '7',
speaker: 'host1',
text: "That's definitely something we need to think about. The author suggests that we might need new frameworks for understanding creativity in the age of AI.",
timestamp: '3:40'
},
{
id: '8',
speaker: 'host2',
text: "I agree. It's not about rejecting AI, but finding ways to integrate it responsibly into our creative workflows while maintaining human oversight and artistic vision.",
timestamp: '4:15'
},
{
id: '9',
speaker: 'host1',
text: "The future looks really exciting! Imagine what we'll be able to create when we embrace these technologies as tools rather than threats.",
timestamp: '4:50'
},
{
id: '10',
speaker: 'host2',
text: "Absolutely! Thanks for sharing this article with our listeners. It really gives us a lot to think about regarding the future of creativity and AI.",
timestamp: '5:20'
}
];