"use client" import { useState, useMemo } from "react" import { useQuery } from "@tanstack/react-query" import { getTracks, getStats } from "@/lib/api" import type { FilterParams, Track } from "@/lib/types" import FilterPanel from "@/components/FilterPanel" import AudioPlayer from "@/components/AudioPlayer" // Helper function to format Discogs genre labels (e.g., "Pop---Ballad" -> ["Pop", "Ballad"]) function formatGenre(genre: string): { category: string; subgenre: string } { const parts = genre.split('---') return { category: parts[0] || genre, subgenre: parts[1] || '' } } // Extract unique values for filter options function extractFilterOptions(tracks: Track[]) { const genres = new Set() const moods = new Set() const instruments = new Set() const keys = new Set() tracks.forEach(track => { // Extract genre category (before "---") const genreCategory = formatGenre(track.classification.genre.primary).category genres.add(genreCategory) // Extract primary mood if (track.classification.mood.primary) { moods.add(track.classification.mood.primary) } // Extract instruments track.classification.instruments?.forEach(instrument => { instruments.add(instrument) }) // Extract key if (track.features.key) { keys.add(track.features.key) } }) return { genres: Array.from(genres).sort(), moods: Array.from(moods).sort(), instruments: Array.from(instruments).sort(), keys: Array.from(keys).sort(), } } export default function Home() { const [filters, setFilters] = useState({}) const [page, setPage] = useState(0) const [currentTrack, setCurrentTrack] = useState(null) const limit = 50 const { data: tracksData, isLoading: isLoadingTracks } = useQuery({ queryKey: ['tracks', filters, page], queryFn: () => getTracks({ ...filters, skip: page * limit, limit }), }) const { data: stats } = useQuery({ queryKey: ['stats'], queryFn: getStats, }) // Extract filter options from displayed tracks // For better UX, we use the current page's tracks to populate filters // TODO: Add a dedicated API endpoint to get all unique filter values const filterOptions = useMemo(() => { if (!tracksData?.tracks || tracksData.tracks.length === 0) { return { genres: [], moods: [], instruments: [], keys: [] } } return extractFilterOptions(tracksData.tracks) }, [tracksData]) return (
{/* Header */}

Audio Classifier

Intelligent music library management

{/* Main Content */}
{/* Stats */} {stats && (

Total Tracks

{stats.total_tracks}

Avg BPM

{stats.average_bpm}

Total Hours

{stats.total_duration_hours}h

Genres

{stats.genres.length}

)} {/* Filter Panel */} { setFilters(newFilters) setPage(0) // Reset to first page when filters change }} availableGenres={filterOptions.genres} availableMoods={filterOptions.moods} availableInstruments={filterOptions.instruments} availableKeys={filterOptions.keys} /> {/* Tracks List */}

Music Library

{tracksData?.total || 0} tracks total

{isLoadingTracks ? (
Loading...
) : tracksData?.tracks.length === 0 ? (
No tracks found. Start by analyzing your audio library!
) : (
{tracksData?.tracks.map((track) => (

{track.filename}

{/* Primary metadata */}
{(() => { const genre = formatGenre(track.classification.genre.primary) return ( <> {genre.category} {genre.subgenre && ( {genre.subgenre} )} ) })()} {track.classification.mood.primary} {Math.round(track.features.tempo_bpm)} BPM {track.features.key} {Math.floor(track.duration_seconds / 60)}:{String(Math.floor(track.duration_seconds % 60)).padStart(2, '0')}
{/* Secondary genres */} {track.classification.genre.secondary && track.classification.genre.secondary.length > 0 && (
Also: {track.classification.genre.secondary.slice(0, 3).map((g, i) => { const genre = formatGenre(g) return ( {genre.category}{genre.subgenre && ` • ${genre.subgenre}`} ) })}
)} {/* Secondary moods */} {track.classification.mood.secondary && track.classification.mood.secondary.length > 0 && (
Moods: {track.classification.mood.secondary.map((mood, i) => ( {mood} ))}
)} {/* Instruments */} {track.classification.instruments && track.classification.instruments.length > 0 && (
Instruments: {track.classification.instruments.slice(0, 6).map((instrument, i) => ( {instrument} ))} {track.classification.instruments.length > 6 && ( +{track.classification.instruments.length - 6} more )}
)}
Download
))}
)} {/* Pagination */} {tracksData && tracksData.total > limit && (
Page {page + 1} of {Math.ceil(tracksData.total / limit)}
)}
{/* Instructions */}

Getting Started

  1. Make sure the backend is running (docker-compose up)
  2. Use the API to analyze your audio library:
                    {`curl -X POST http://localhost:8000/api/analyze/folder \\
      -H "Content-Type: application/json" \\
      -d '{"path": "/audio/your_music", "recursive": true}'`}
                  
  3. Refresh this page to see your analyzed tracks
{/* Fixed Audio Player at bottom - always visible */}
) }