mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-18 22:37:51 +00:00
154 lines
4.8 KiB
TypeScript
154 lines
4.8 KiB
TypeScript
import React, { useState, useRef } from 'react';
|
|
import { Mic, Square, Loader, Play, RotateCcw } from 'lucide-react';
|
|
|
|
interface AudioRecorderDemoProps {
|
|
onAnalysisComplete: (result: {
|
|
fluency: number;
|
|
accuracy: number;
|
|
confidence: number;
|
|
feedback: string;
|
|
}) => void;
|
|
}
|
|
|
|
export function AudioRecorderDemo({ onAnalysisComplete }: AudioRecorderDemoProps) {
|
|
const [isRecording, setIsRecording] = useState(false);
|
|
const [audioBlob, setAudioBlob] = useState<Blob | null>(null);
|
|
const [isAnalyzing, setIsAnalyzing] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
|
|
const chunksRef = useRef<Blob[]>([]);
|
|
|
|
const startRecording = async () => {
|
|
try {
|
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
mediaRecorderRef.current = new MediaRecorder(stream);
|
|
chunksRef.current = [];
|
|
|
|
mediaRecorderRef.current.ondataavailable = (e) => {
|
|
chunksRef.current.push(e.data);
|
|
};
|
|
|
|
mediaRecorderRef.current.onstop = () => {
|
|
const audioBlob = new Blob(chunksRef.current, { type: 'audio/webm' });
|
|
setAudioBlob(audioBlob);
|
|
};
|
|
|
|
mediaRecorderRef.current.start();
|
|
setIsRecording(true);
|
|
setError(null);
|
|
} catch (err) {
|
|
setError('Erro ao acessar microfone. Verifique as permissões.');
|
|
console.error('Erro ao iniciar gravação:', err);
|
|
}
|
|
};
|
|
|
|
const stopRecording = () => {
|
|
if (mediaRecorderRef.current && isRecording) {
|
|
mediaRecorderRef.current.stop();
|
|
setIsRecording(false);
|
|
|
|
mediaRecorderRef.current.stream.getTracks().forEach(track => track.stop());
|
|
}
|
|
};
|
|
|
|
const analyzeAudio = async () => {
|
|
if (!audioBlob) return;
|
|
|
|
setIsAnalyzing(true);
|
|
setError(null);
|
|
|
|
try {
|
|
// Simulação de análise para demo
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
|
|
// Resultados simulados para demonstração
|
|
onAnalysisComplete({
|
|
fluency: Math.floor(Math.random() * 20) + 80, // 80-100
|
|
accuracy: Math.floor(Math.random() * 15) + 85, // 85-100
|
|
confidence: Math.floor(Math.random() * 25) + 75, // 75-100
|
|
feedback: "Excelente leitura! Sua fluência está muito boa e você demonstra confiança na pronúncia. Continue praticando para melhorar ainda mais."
|
|
});
|
|
} catch (err) {
|
|
setError('Erro ao analisar áudio. Tente novamente.');
|
|
console.error('Erro na análise:', err);
|
|
} finally {
|
|
setIsAnalyzing(false);
|
|
}
|
|
};
|
|
|
|
const resetRecording = () => {
|
|
setAudioBlob(null);
|
|
setError(null);
|
|
};
|
|
|
|
return (
|
|
<div className="p-6 bg-gray-50 rounded-xl">
|
|
<div className="flex flex-wrap items-center gap-4 justify-center">
|
|
{!isRecording && !audioBlob && (
|
|
<button
|
|
onClick={startRecording}
|
|
className="flex items-center gap-2 px-6 py-3 bg-red-600 text-white rounded-xl hover:bg-red-700 transition"
|
|
>
|
|
<Mic className="w-5 h-5" />
|
|
Iniciar Gravação
|
|
</button>
|
|
)}
|
|
|
|
{isRecording && (
|
|
<button
|
|
onClick={stopRecording}
|
|
className="flex items-center gap-2 px-6 py-3 bg-gray-600 text-white rounded-xl hover:bg-gray-700 transition"
|
|
>
|
|
<Square className="w-5 h-5" />
|
|
Parar Gravação
|
|
</button>
|
|
)}
|
|
|
|
{audioBlob && !isAnalyzing && (
|
|
<>
|
|
<button
|
|
onClick={() => {
|
|
const url = URL.createObjectURL(audioBlob);
|
|
const audio = new Audio(url);
|
|
audio.play();
|
|
}}
|
|
className="flex items-center gap-2 px-6 py-3 bg-purple-600 text-white rounded-xl hover:bg-purple-700 transition"
|
|
>
|
|
<Play className="w-5 h-5" />
|
|
Ouvir
|
|
</button>
|
|
|
|
<button
|
|
onClick={analyzeAudio}
|
|
className="flex items-center gap-2 px-6 py-3 bg-green-600 text-white rounded-xl hover:bg-green-700 transition"
|
|
>
|
|
Analisar Leitura
|
|
</button>
|
|
|
|
<button
|
|
onClick={resetRecording}
|
|
className="flex items-center gap-2 px-6 py-3 bg-gray-200 text-gray-600 rounded-xl hover:bg-gray-300 transition"
|
|
>
|
|
<RotateCcw className="w-5 h-5" />
|
|
Recomeçar
|
|
</button>
|
|
</>
|
|
)}
|
|
|
|
{isAnalyzing && (
|
|
<div className="flex items-center gap-2 text-gray-600">
|
|
<Loader className="w-5 h-5 animate-spin" />
|
|
Analisando sua leitura...
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="mt-4 text-red-600 text-sm text-center">
|
|
{error}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|