- 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>
318 lines
12 KiB
TypeScript
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'
|
|
}
|
|
];
|