Files
breakpilot-lehrer/studio-v2/components/voice/VoiceIndicator.tsx
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

91 lines
2.1 KiB
TypeScript

'use client'
import { useEffect, useState } from 'react'
interface VoiceIndicatorProps {
isListening: boolean
audioLevel?: number // 0-100
status?: string
}
/**
* Visual indicator for voice activity
* Shows audio level and status
*/
export function VoiceIndicator({
isListening,
audioLevel = 0,
status = 'idle',
}: VoiceIndicatorProps) {
const [bars, setBars] = useState<number[]>([0, 0, 0, 0, 0])
// Animate bars based on audio level
useEffect(() => {
if (!isListening) {
setBars([0, 0, 0, 0, 0])
return
}
const interval = setInterval(() => {
setBars((prev) =>
prev.map(() => {
const base = audioLevel / 100
const variance = Math.random() * 0.4
return Math.min(1, base + variance)
})
)
}, 100)
return () => clearInterval(interval)
}, [isListening, audioLevel])
const statusColors: Record<string, string> = {
idle: 'bg-gray-400',
connected: 'bg-blue-500',
listening: 'bg-green-500',
processing: 'bg-yellow-500',
responding: 'bg-purple-500',
error: 'bg-red-500',
}
const statusLabels: Record<string, string> = {
idle: 'Bereit',
connected: 'Verbunden',
listening: 'Hoert zu...',
processing: 'Verarbeitet...',
responding: 'Antwortet...',
error: 'Fehler',
}
return (
<div className="flex items-center gap-3">
{/* Status dot */}
<div
className={`w-3 h-3 rounded-full ${statusColors[status] || statusColors.idle} ${
isListening ? 'animate-pulse' : ''
}`}
/>
{/* Audio level bars */}
<div className="flex items-end gap-0.5 h-6">
{bars.map((level, i) => (
<div
key={i}
className={`w-1 rounded-full transition-all duration-100 ${
isListening ? 'bg-green-500' : 'bg-gray-300'
}`}
style={{
height: `${Math.max(4, level * 24)}px`,
}}
/>
))}
</div>
{/* Status text */}
<span className="text-sm text-gray-600">
{statusLabels[status] || status}
</span>
</div>
)
}