Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 19 additions & 16 deletions extras/speaker-recognition/webui/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
import { UserProvider } from './contexts/UserContext'
import { ThemeProvider } from './contexts/ThemeContext'
import Layout from './components/layout/Layout'
import AudioViewer from './pages/AudioViewer'
import Annotation from './pages/Annotation'
Expand All @@ -12,22 +13,24 @@ import './App.css'

function App() {
return (
<UserProvider>
<Router future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
<Layout>
<Routes>
<Route path="/" element={<AudioViewer />} />
<Route path="/audio" element={<AudioViewer />} />
<Route path="/annotation" element={<Annotation />} />
<Route path="/enrollment" element={<Enrollment />} />
<Route path="/speakers" element={<Speakers />} />
<Route path="/inference" element={<Inference />} />
<Route path="/infer-live" element={<InferLive />} />
<Route path="/infer-live-simple" element={<InferLiveSimplified />} />
</Routes>
</Layout>
</Router>
</UserProvider>
<ThemeProvider>
<UserProvider>
<Router future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
<Layout>
<Routes>
<Route path="/" element={<AudioViewer />} />
<Route path="/audio" element={<AudioViewer />} />
<Route path="/annotation" element={<Annotation />} />
<Route path="/enrollment" element={<Enrollment />} />
<Route path="/speakers" element={<Speakers />} />
<Route path="/inference" element={<Inference />} />
<Route path="/infer-live" element={<InferLive />} />
<Route path="/infer-live-simple" element={<InferLiveSimplified />} />
</Routes>
</Layout>
</Router>
</UserProvider>
</ThemeProvider>
)
}

Expand Down
21 changes: 21 additions & 0 deletions extras/speaker-recognition/webui/src/components/ThemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react'
import { Sun, Moon } from 'lucide-react'
import { useTheme } from '../contexts/ThemeContext'

export default function ThemeToggle() {
const { isDark, toggleTheme } = useTheme()

return (
<button
onClick={toggleTheme}
className="flex items-center justify-center w-8 h-8 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
title={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
>
{isDark ? (
<Sun className="h-5 w-5 text-yellow-500" />
) : (
<Moon className="h-5 w-5 text-gray-600 dark:text-gray-300" />
)}
</button>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export default function UserSelector() {

{/* Dropdown Menu */}
{isDropdownOpen && (
<div className="absolute right-0 mt-2 w-64 bg-white rounded-md shadow-lg border z-50">
<div className="absolute right-0 mt-2 w-64 bg-white dark:bg-gray-800 rounded-md shadow-lg border border-gray-200 dark:border-gray-700 z-50">
<div className="py-2">
{/* Existing Users */}
{users.length > 0 && (
Expand Down
22 changes: 12 additions & 10 deletions extras/speaker-recognition/webui/src/components/layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Link, useLocation } from 'react-router-dom'
import { Mic, Music, FileText, Users, Brain, User, Radio, Wifi } from 'lucide-react'
import UserSelector from '../UserSelector'
import ConnectionStatus from '../ConnectionStatus'
import ThemeToggle from '../ThemeToggle'

interface LayoutProps {
children: React.ReactNode
Expand All @@ -22,20 +23,21 @@ export default function Layout({ children }: LayoutProps) {
]

return (
<div className="min-h-screen bg-gray-50">
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
{/* Header */}
<header className="bg-white shadow-sm border-b border-gray-200">
<header className="bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
<div className="flex items-center space-x-4">
<Mic className="h-8 w-8 text-blue-600" />
<h1 className="text-xl font-semibold text-gray-900">
<h1 className="text-xl font-semibold text-gray-900 dark:text-gray-100">
Speaker Recognition System
</h1>
</div>
<div className="flex items-center space-x-4">
<ThemeToggle />
<ConnectionStatus />
<div className="border-l border-gray-300 h-6"></div>
<div className="border-l border-gray-300 dark:border-gray-600 h-6"></div>
<UserSelector />
</div>
</div>
Expand All @@ -46,16 +48,16 @@ export default function Layout({ children }: LayoutProps) {
<div className="flex flex-col lg:flex-row gap-8">
{/* Sidebar Navigation */}
<nav className="lg:w-64 flex-shrink-0">
<div className="card p-4">
<div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4 shadow-sm">
<ul className="space-y-2">
{navigationItems.map(({ path, label, icon: Icon }) => (
<li key={path}>
<Link
to={path}
className={`flex items-center space-x-3 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
location.pathname === path
? 'bg-blue-100 text-blue-900'
: 'text-gray-700 hover:bg-gray-100'
? 'bg-blue-100 dark:bg-blue-900 text-blue-900 dark:text-blue-100 border-l-4 border-blue-500 dark:border-blue-400'
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-100'
}`}
>
<Icon className="h-5 w-5" />
Expand All @@ -69,17 +71,17 @@ export default function Layout({ children }: LayoutProps) {

{/* Main Content */}
<main className="flex-1 min-w-0">
<div className="card p-6">
<div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-6 shadow-sm">
{children}
</div>
</main>
</div>
</div>

{/* Footer */}
<footer className="bg-white border-t border-gray-200 mt-auto">
<footer className="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 mt-auto">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div className="text-center text-sm text-gray-500">
<div className="text-center text-sm text-gray-500 dark:text-gray-400">
🎤 Speaker Recognition System v0.1.0 | Built with React and PyTorch
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,24 @@ export function SessionStats({ stats, isRecording }: SessionStatsProps) {
}

return (
<div className="bg-white border rounded-lg p-4">
<div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-6">
<div className="flex items-center space-x-2">
<Clock className="h-4 w-4 text-gray-500" />
<span className="text-sm text-gray-900">
<Clock className="h-4 w-4 text-gray-500 dark:text-gray-400" />
<span className="text-sm text-gray-900 dark:text-gray-100">
{formatDuration(stats.sessionDuration)}
</span>
</div>
<div className="flex items-center space-x-2">
<Volume2 className="h-4 w-4 text-gray-500" />
<span className="text-sm text-gray-900">
<Volume2 className="h-4 w-4 text-gray-500 dark:text-gray-400" />
<span className="text-sm text-gray-900 dark:text-gray-100">
{stats.totalWords} words
</span>
</div>
<div className="flex items-center space-x-2">
<Users className="h-4 w-4 text-gray-500" />
<span className="text-sm text-gray-900">
<Users className="h-4 w-4 text-gray-500 dark:text-gray-400" />
<span className="text-sm text-gray-900 dark:text-gray-100">
{stats.identifiedSpeakers.size} speakers
</span>
</div>
Expand Down
26 changes: 13 additions & 13 deletions extras/speaker-recognition/webui/src/pages/Annotation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,7 @@ export default function Annotation() {
{/* File Upload */}
<div className="grid md:grid-cols-2 gap-6">
<div className="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-6">
<h3 className="text-lg font-medium mb-4">📁 Upload Audio File</h3>
<h3 className="text-lg font-medium mb-4 text-gray-900 dark:text-gray-100">📁 Upload Audio File</h3>
<FileUploader
onUpload={handleFileUpload}
accept=".wav,.flac,.mp3,.m4a,.ogg"
Expand All @@ -799,7 +799,7 @@ export default function Annotation() {

{/* Import JSON */}
<div className="border-2 border-dashed border-blue-300 dark:border-blue-600 rounded-lg p-6">
<h3 className="text-lg font-medium mb-4">📄 Import Deepgram JSON</h3>
<h3 className="text-lg font-medium mb-4 text-gray-900 dark:text-gray-100">📄 Import Deepgram JSON</h3>
<FileUploader
onUpload={handleDeepgramUpload}
accept=".json"
Expand All @@ -815,7 +815,7 @@ export default function Annotation() {

{/* Processing Mode Selector - Full Width Section */}
<div className="border-2 border-dashed border-green-300 dark:border-green-600 rounded-lg p-6">
<h3 className="text-xl font-medium mb-4 text-gray-900 dark:text-gray-100">🎯 Speaker Processing Modes</h3>
<h3 className="text-xl font-medium mb-4 text-gray-900 dark:text-gray-50">🎯 Speaker Processing Modes</h3>

{audioData ? (
<ProcessingModeSelector
Expand Down Expand Up @@ -857,15 +857,15 @@ export default function Annotation() {
{isLoading && (
<div className="text-center py-8">
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<p className="mt-2 text-gray-600">Processing audio file...</p>
<p className="mt-2 text-gray-600 dark:text-gray-300">Processing audio file...</p>
</div>
)}

{audioData && (
<>
{/* Audio Information */}
<div className="card-secondary p-4">
<h3 className="text-lg font-medium mb-2">🎵 {audioData.file.name}</h3>
<h3 className="text-lg font-medium mb-2 text-gray-900 dark:text-gray-100">🎵 {audioData.file.name}</h3>
<div className="grid grid-cols-3 gap-4 text-sm">
<div>Duration: {formatDuration(audioData.buffer.duration * 1000)}</div>
<div>Sample Rate: {(audioData.buffer.sampleRate / 1000).toFixed(1)} kHz</div>
Expand All @@ -876,7 +876,7 @@ export default function Annotation() {
{/* Waveform with segments */}
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium">📊 Timeline</h3>
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100">📊 Timeline</h3>
<button
onClick={addManualSegment}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
Expand Down Expand Up @@ -908,7 +908,7 @@ export default function Annotation() {

{/* Segments List */}
<div className="space-y-4">
<h3 className="text-lg font-medium">🗣️ Speaker Segments</h3>
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100">🗣️ Speaker Segments</h3>

{/* Filter Controls */}
{segments.length > 0 && (
Expand All @@ -920,7 +920,7 @@ export default function Annotation() {
className="flex items-center space-x-2 px-3 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors"
>
<Filter className="h-4 w-4" />
<span className="text-sm">Filter Speakers</span>
<span className="text-sm text-gray-900 dark:text-gray-100">Filter Speakers</span>
{selectedSpeakers.size > 0 && (
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{selectedSpeakers.size}
Expand Down Expand Up @@ -957,7 +957,7 @@ export default function Annotation() {
className="rounded"
/>
<span className="text-sm">{speaker}</span>
<span className="text-xs text-gray-500">({count})</span>
<span className="text-xs text-gray-500 dark:text-gray-400">({count})</span>
</label>
))}
</div>
Expand All @@ -976,7 +976,7 @@ export default function Annotation() {
e.stopPropagation()
clearAllSpeakers()
}}
className="flex-1 px-2 py-1 text-xs bg-gray-100 text-gray-800 rounded hover:bg-gray-200"
className="flex-1 px-2 py-1 text-xs bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded hover:bg-gray-200 dark:hover:bg-gray-600"
>
Clear All
</button>
Expand Down Expand Up @@ -1061,7 +1061,7 @@ export default function Annotation() {
step="0.1"
min={minDuration}
/>
<span className="text-sm text-gray-500">
<span className="text-sm text-gray-500 dark:text-gray-400">
({filteredSegments.length} segments)
</span>
</div>
Expand Down Expand Up @@ -1154,7 +1154,7 @@ export default function Annotation() {
}`}>
{speakerLabel}
</span>
<span className="text-sm text-gray-600">
<span className="text-sm text-gray-600 dark:text-gray-300">
{speakerSegments.length} segments • {formatDuration(totalDuration * 1000)}
</span>
</div>
Expand Down Expand Up @@ -1251,7 +1251,7 @@ export default function Annotation() {
<h4 className="font-medium text-primary">🔍 Debug Output</h4>
<button
onClick={() => setShowJsonOutput(!showJsonOutput)}
className="flex items-center space-x-2 px-3 py-1 text-sm bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200"
className="flex items-center space-x-2 px-3 py-1 text-sm bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200 rounded-md hover:bg-gray-200 dark:hover:bg-gray-600"
>
<span>{showJsonOutput ? 'Hide' : 'Show'} JSON</span>
</button>
Expand Down
12 changes: 6 additions & 6 deletions extras/speaker-recognition/webui/src/pages/Enrollment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ export default function Enrollment() {
{/* Recording Section */}
<div className="grid md:grid-cols-2 gap-6 mb-6">
<div className="border rounded-lg p-4">
<h3 className="text-lg font-medium mb-4">🎤 Record Audio</h3>
<h3 className="text-lg font-medium mb-4 text-gray-900 dark:text-gray-100">🎤 Record Audio</h3>
<div className="text-center space-y-4">
{!isRecording ? (
<button
Expand Down Expand Up @@ -586,7 +586,7 @@ export default function Enrollment() {

{/* File Upload Section */}
<div className="border rounded-lg p-4">
<h3 className="text-lg font-medium mb-4">📁 Upload Audio</h3>
<h3 className="text-lg font-medium mb-4 text-gray-900 dark:text-gray-100">📁 Upload Audio</h3>
<FileUploader
onUpload={handleFileUpload}
accept=".wav"
Expand All @@ -599,7 +599,7 @@ export default function Enrollment() {
{currentSession.audioFiles.length > 0 && (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium">Audio Samples</h3>
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100">Audio Samples</h3>
<span className="text-sm text-green-600 bg-green-100 px-2 py-1 rounded-full">
💾 Files will be saved during enrollment
</span>
Expand Down Expand Up @@ -652,7 +652,7 @@ export default function Enrollment() {

{/* Quality Guidelines */}
<div className="mt-6 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<h4 className="font-medium text-blue-800 mb-2">Quality Guidelines</h4>
<h4 className="font-medium text-blue-800 dark:text-blue-200 mb-2">Quality Guidelines</h4>
<ul className="text-sm text-blue-700 space-y-1">
<li>• Excellent: 60+ seconds, 5+ samples, 30+ dB SNR</li>
<li>• Good: 30+ seconds, 3+ samples, 20+ dB SNR</li>
Expand All @@ -667,15 +667,15 @@ export default function Enrollment() {
{/* Previous Sessions */}
{sessions.length > 0 && !currentSession && (
<div className="space-y-4">
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">Previous Enrollments</h2>
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-50">Previous Enrollments</h2>
<div className="space-y-3">
{sessions.map((session) => (
<div
key={session.id}
className="flex items-center justify-between p-4 bg-gray-50 rounded-lg"
>
<div>
<p className="font-medium text-gray-900 dark:text-gray-100">{session.speakerName}</p>
<p className="font-medium text-gray-900 dark:text-gray-50">{session.speakerName}</p>
<div className="flex items-center space-x-3 text-sm text-gray-500 dark:text-gray-400">
<span>{session.audioFiles.length} samples</span>
<span>{formatDuration(session.totalDuration)}</span>
Expand Down
Loading