Spaces:
Sleeping
Sleeping
| import { useState, useEffect } from "react"; | |
| import { useAuth } from "@/_core/hooks/useAuth"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; | |
| import { Music, Upload, Loader2, FileAudio, ArrowRight } from "lucide-react"; | |
| import { getLoginUrl, getAuthConfig, getHfLoginUrl, getDevLoginUrl, type AuthConfig } from "@/const"; | |
| import { trpc } from "@/lib/trpc"; | |
| import { useLocation } from "wouter"; | |
| export default function Home() { | |
| const { user, loading, isAuthenticated } = useAuth(); | |
| const [, setLocation] = useLocation(); | |
| const tracks = trpc.tracks.list.useQuery(undefined, { enabled: isAuthenticated }); | |
| const [authConfig, setAuthConfig] = useState<AuthConfig | null>(null); | |
| // Fetch auth config on mount | |
| useEffect(() => { | |
| getAuthConfig().then(setAuthConfig); | |
| }, []); | |
| if (loading) { | |
| return ( | |
| <div className="min-h-screen flex items-center justify-center"> | |
| <Loader2 className="w-8 h-8 animate-spin text-primary" /> | |
| </div> | |
| ); | |
| } | |
| if (!isAuthenticated) { | |
| return ( | |
| <div className="min-h-screen bg-gradient-to-br from-purple-50 via-white to-blue-50"> | |
| <div className="container py-16"> | |
| <div className="max-w-4xl mx-auto"> | |
| <div className="text-center mb-12"> | |
| <div className="inline-flex items-center justify-center w-16 h-16 bg-primary/10 rounded-2xl mb-6"> | |
| <Music className="w-8 h-8 text-primary" /> | |
| </div> | |
| <h1 className="text-5xl font-bold mb-4 bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-transparent"> | |
| AI Music Attribution System | |
| </h1> | |
| <p className="text-xl text-muted-foreground max-w-2xl mx-auto"> | |
| Map AI-generated music stems to their original training sources using advanced audio fingerprinting and attribution methods | |
| </p> | |
| </div> | |
| <div className="grid md:grid-cols-3 gap-6 mb-12"> | |
| <Card> | |
| <CardHeader> | |
| <div className="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center mb-3"> | |
| <FileAudio className="w-6 h-6 text-purple-600" /> | |
| </div> | |
| <CardTitle className="text-lg">Stem Separation</CardTitle> | |
| <CardDescription> | |
| Automatically separate audio into vocals, drums, bass, and other instruments using Demucs | |
| </CardDescription> | |
| </CardHeader> | |
| </Card> | |
| <Card> | |
| <CardHeader> | |
| <div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-3"> | |
| <Music className="w-6 h-6 text-blue-600" /> | |
| </div> | |
| <CardTitle className="text-lg">Audio Fingerprinting</CardTitle> | |
| <CardDescription> | |
| Generate unique fingerprints for each stem using Chromaprint technology | |
| </CardDescription> | |
| </CardHeader> | |
| </Card> | |
| <Card> | |
| <CardHeader> | |
| <div className="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center mb-3"> | |
| <ArrowRight className="w-6 h-6 text-green-600" /> | |
| </div> | |
| <CardTitle className="text-lg">Source Attribution</CardTitle> | |
| <CardDescription> | |
| Match stems to training tracks using CLAP embeddings and FAISS similarity search | |
| </CardDescription> | |
| </CardHeader> | |
| </Card> | |
| </div> | |
| <div className="text-center"> | |
| {/* Standard OAuth configured */} | |
| {getLoginUrl() !== "#oauth-not-configured" && ( | |
| <> | |
| <Button size="lg" asChild> | |
| <a href={getLoginUrl()}> | |
| Get Started | |
| <ArrowRight className="ml-2 w-4 h-4" /> | |
| </a> | |
| </Button> | |
| <p className="text-sm text-muted-foreground mt-4"> | |
| Sign in to upload and analyze your AI-generated music | |
| </p> | |
| </> | |
| )} | |
| {/* No standard OAuth - show login options based on config */} | |
| {getLoginUrl() === "#oauth-not-configured" && ( | |
| <> | |
| {/* HuggingFace OAuth (for HF Spaces) */} | |
| {authConfig?.hfOAuthEnabled && ( | |
| <> | |
| <Button size="lg" asChild> | |
| <a href={getHfLoginUrl()}> | |
| Sign in with HuggingFace | |
| <ArrowRight className="ml-2 w-4 h-4" /> | |
| </a> | |
| </Button> | |
| <p className="text-sm text-muted-foreground mt-4"> | |
| Sign in with your HuggingFace account to continue | |
| </p> | |
| </> | |
| )} | |
| {/* Dev/Demo login - show as fallback */} | |
| {!authConfig?.hfOAuthEnabled && ( | |
| <> | |
| <Button size="lg" asChild> | |
| <a href={getDevLoginUrl()}> | |
| Enter Demo | |
| <ArrowRight className="ml-2 w-4 h-4" /> | |
| </a> | |
| </Button> | |
| <p className="text-sm text-muted-foreground mt-4"> | |
| {authConfig === null ? "Loading..." : "Click to access the demo"} | |
| </p> | |
| </> | |
| )} | |
| </> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className="min-h-screen bg-gradient-to-br from-purple-50 via-white to-blue-50"> | |
| <div className="container py-8"> | |
| <div className="max-w-6xl mx-auto"> | |
| <div className="flex items-center justify-between mb-8"> | |
| <div> | |
| <h1 className="text-3xl font-bold mb-2">My Tracks</h1> | |
| <p className="text-muted-foreground"> | |
| Upload AI-generated music to analyze its training data sources | |
| </p> | |
| </div> | |
| <Button onClick={() => setLocation("/upload")}> | |
| <Upload className="mr-2 w-4 h-4" /> | |
| Upload Track | |
| </Button> | |
| </div> | |
| {tracks.isLoading ? ( | |
| <div className="flex items-center justify-center py-12"> | |
| <Loader2 className="w-8 h-8 animate-spin text-primary" /> | |
| </div> | |
| ) : tracks.data && tracks.data.length > 0 ? ( | |
| <div className="grid gap-4"> | |
| {tracks.data.map((track) => ( | |
| <Card | |
| key={track.id} | |
| className="hover:shadow-md transition-shadow cursor-pointer" | |
| onClick={() => setLocation(`/track/${track.id}`)} | |
| > | |
| <CardHeader> | |
| <div className="flex items-start justify-between"> | |
| <div className="flex-1"> | |
| <CardTitle className="text-xl mb-1">{track.title}</CardTitle> | |
| {track.artist && ( | |
| <CardDescription className="text-base">{track.artist}</CardDescription> | |
| )} | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| {track.status === "pending" && ( | |
| <span className="text-sm text-yellow-600 bg-yellow-50 px-3 py-1 rounded-full"> | |
| Pending | |
| </span> | |
| )} | |
| {track.status === "processing" && ( | |
| <span className="text-sm text-blue-600 bg-blue-50 px-3 py-1 rounded-full flex items-center gap-1"> | |
| <Loader2 className="w-3 h-3 animate-spin" /> | |
| Processing | |
| </span> | |
| )} | |
| {track.status === "completed" && ( | |
| <span className="text-sm text-green-600 bg-green-50 px-3 py-1 rounded-full"> | |
| Completed | |
| </span> | |
| )} | |
| {track.status === "failed" && ( | |
| <span className="text-sm text-red-600 bg-red-50 px-3 py-1 rounded-full"> | |
| Failed | |
| </span> | |
| )} | |
| </div> | |
| </div> | |
| <div className="text-sm text-muted-foreground mt-2"> | |
| Uploaded {new Date(track.createdAt).toLocaleDateString()} | |
| </div> | |
| </CardHeader> | |
| </Card> | |
| ))} | |
| </div> | |
| ) : ( | |
| <Card> | |
| <CardContent className="py-12 text-center"> | |
| <Music className="w-12 h-12 text-muted-foreground mx-auto mb-4" /> | |
| <h3 className="text-lg font-semibold mb-2">No tracks yet</h3> | |
| <p className="text-muted-foreground mb-6"> | |
| Upload your first AI-generated track to get started | |
| </p> | |
| <Button onClick={() => setLocation("/upload")}> | |
| <Upload className="mr-2 w-4 h-4" /> | |
| Upload Track | |
| </Button> | |
| </CardContent> | |
| </Card> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |