This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/studio-v2/components/voice/VoiceIndicator.tsx
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +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>
)
}