EmberClone/apps/web/src/components/VoiceInput.tsx

92 lines
2.6 KiB
TypeScript

import React, { useState, useEffect, useRef } from 'react';
import { Mic, MicOff, Loader2 } from 'lucide-react';
import { toast } from 'sonner';
interface VoiceInputProps {
onTranscribed: (text: string) => void;
className?: string;
}
export default function VoiceInput({ onTranscribed, className = "" }: VoiceInputProps) {
const [isListening, setIsListening] = useState(false);
const [isSupported, setIsSupported] = useState(true);
const recognitionRef = useRef<any>(null);
useEffect(() => {
const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;
if (!SpeechRecognition) {
setIsSupported(false);
return;
}
const recognition = new SpeechRecognition();
recognition.lang = 'de-DE';
recognition.interimResults = false;
recognition.continuous = false;
recognition.onresult = (event: any) => {
const text = event.results[0][0].transcript;
onTranscribed(text);
setIsListening(false);
};
recognition.onerror = (event: any) => {
console.error('Speech recognition error:', event.error);
toast.error(`Spracherkennung Fehler: ${event.error}`);
setIsListening(false);
};
recognition.onend = () => {
setIsListening(false);
};
recognitionRef.current = recognition;
}, [onTranscribed]);
const toggleListening = () => {
if (!isSupported) {
toast.error("Spracherkennung wird von diesem Browser nicht unterstützt.");
return;
}
if (isListening) {
recognitionRef.current?.stop();
setIsListening(false);
} else {
try {
recognitionRef.current?.start();
setIsListening(true);
} catch (e) {
console.error(e);
setIsListening(false);
}
}
};
return (
<button
onClick={toggleListening}
disabled={!isSupported}
className={`
relative p-2 rounded-full transition-all duration-200
${isListening
? 'bg-red-500 text-white animate-pulse ring-4 ring-red-500/20'
: 'bg-slate-100 text-slate-600 hover:bg-slate-200 dark:bg-slate-800 dark:text-slate-400 dark:hover:bg-slate-700'
}
${className}
`}
title={isListening ? "Stoppen" : "Spracheingabe starten"}
>
{isListening ? (
<Loader2 className="w-5 h-5 animate-spin" />
) : (
<Mic className="w-5 h-5" />
)}
{!isSupported && (
<div className="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full border-2 border-white dark:border-slate-900" />
)}
</button>
);
}