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>
91 lines
2.1 KiB
TypeScript
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>
|
|
)
|
|
}
|