From 88fed85a137498c24963e28fbd5b06e49dc7a4e8 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Sun, 5 Apr 2026 10:56:06 +0200 Subject: [PATCH] Add Micro auto --- frontend/src/App.jsx | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 4241bcc..b458bb0 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -4,6 +4,7 @@ const API_BASE = '/api' const AUTO_STOP_SILENCE_MS = 2500 const SPEECH_START_THRESHOLD = 0.02 const SILENCE_THRESHOLD = 0.012 +const DEBUG_AUDIO = true async function parseApiResponse(res) { const contentType = res.headers.get('content-type') || '' @@ -92,6 +93,7 @@ export default function App() { const [voiceStatus, setVoiceStatus] = useState('') const [errorMessage, setErrorMessage] = useState('') const [micLevel, setMicLevel] = useState(0) + const [audioDebug, setAudioDebug] = useState([]) const [voices, setVoices] = useState([]) const [selectedVoiceURI, setSelectedVoiceURI] = useState('') const mediaRecorderRef = useRef(null) @@ -122,6 +124,12 @@ export default function App() { loadStudents() }, []) + function pushAudioDebug(message) { + if (!DEBUG_AUDIO) return + const timestamp = new Date().toLocaleTimeString('fr-FR', { hour12: false }) + setAudioDebug((prev) => [`${timestamp} ${message}`, ...prev].slice(0, 12)) + } + useEffect(() => { isRecordingRef.current = isRecording }, [isRecording]) @@ -275,6 +283,7 @@ export default function App() { } function stopMediaStream() { + pushAudioDebug('Arrêt et nettoyage du flux micro') if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current) animationFrameRef.current = null @@ -324,6 +333,7 @@ export default function App() { const extension = mimeType.includes('mp4') ? 'mp4' : mimeType.includes('ogg') ? 'ogg' : 'webm' const formData = new FormData() formData.append('file', new File([audioBlob], `voice-input.${extension}`, { type: mimeType || 'audio/webm' })) + pushAudioDebug(`Transcription demandée, taille=${audioBlob.size}, mime=${mimeType || 'inconnu'}`) setIsTranscribing(true) setVoiceStatus('Transcription en cours...') @@ -335,6 +345,7 @@ export default function App() { }) const transcript = (data.text || '').trim() setInput(transcript) + pushAudioDebug(`Transcription reçue, longueur=${transcript.length}`) if (!transcript) { setVoiceStatus('Aucun texte reconnu.') @@ -343,12 +354,14 @@ export default function App() { if (autoSend && selectedStudentId) { setVoiceStatus('Texte reconnu, envoi automatique...') + pushAudioDebug('Envoi automatique du message transcrit') await submitUserMessage(transcript) setVoiceStatus('Message vocal envoyé automatiquement.') } else { setVoiceStatus('Texte dicté prêt à être envoyé.') } } catch (error) { + pushAudioDebug(`Erreur transcription: ${error.message || 'inconnue'}`) setVoiceStatus(error.message || 'Impossible de transcrire cet enregistrement.') } finally { setIsTranscribing(false) @@ -381,10 +394,12 @@ export default function App() { recordingMimeTypeRef.current = mimeType || recorder.mimeType || 'audio/webm' silenceStartedAtRef.current = null hasSpeechInSegmentRef.current = false + pushAudioDebug(`Démarrage enregistrement segment, mime=${recordingMimeTypeRef.current}`) recorder.ondataavailable = (event) => { if (event.data && event.data.size > 0) { recordedChunksRef.current.push(event.data) + pushAudioDebug(`Chunk audio reçu, taille=${event.data.size}`) } } @@ -394,6 +409,7 @@ export default function App() { recordedChunksRef.current = [] mediaRecorderRef.current = null setIsRecording(false) + pushAudioDebug(`Enregistrement arrêté, taille totale=${audioBlob.size}, parole détectée=${hasSpeechInSegmentRef.current ? 'oui' : 'non'}`) if (audioBlob.size > 0 && hasSpeechInSegmentRef.current) { await transcribeRecording(audioBlob, finalMimeType, { autoSend: true }) @@ -405,6 +421,7 @@ export default function App() { } recorder.onerror = () => { + pushAudioDebug('Erreur MediaRecorder') setVoiceStatus('Le navigateur a rencontré une erreur pendant l’enregistrement.') setIsRecording(false) } @@ -418,6 +435,7 @@ export default function App() { const recorder = mediaRecorderRef.current if (recorder && recorder.state !== 'inactive') { silenceStartedAtRef.current = null + pushAudioDebug('Arrêt du segment demandé après silence') recorder.stop() setVoiceStatus(statusMessage) } @@ -431,12 +449,17 @@ export default function App() { setMicLevel(volume) if (volume >= SPEECH_START_THRESHOLD) { + if (!hasSpeechInSegmentRef.current) { + pushAudioDebug(`Parole détectée, niveau=${volume.toFixed(4)}`) + } hasSpeechInSegmentRef.current = true silenceStartedAtRef.current = null } else if (isRecordingRef.current && hasSpeechInSegmentRef.current && volume <= SILENCE_THRESHOLD) { if (!silenceStartedAtRef.current) { + pushAudioDebug(`Début silence, niveau=${volume.toFixed(4)}`) silenceStartedAtRef.current = now } else if (now - silenceStartedAtRef.current >= AUTO_STOP_SILENCE_MS) { + pushAudioDebug('Silence confirmé, arrêt du segment') stopSegmentRecording() } } else if (volume > SILENCE_THRESHOLD) { @@ -449,10 +472,12 @@ export default function App() { async function activateAutoListening() { if (!navigator.mediaDevices?.getUserMedia || typeof MediaRecorder === 'undefined') { setVoiceStatus('Le micro automatique n’est pas pris en charge par ce navigateur.') + pushAudioDebug('Navigateur incompatible micro auto') return } try { + pushAudioDebug('Demande accès micro') const stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, @@ -465,6 +490,7 @@ export default function App() { if (audioContext.state === 'suspended') { await audioContext.resume() } + pushAudioDebug(`Micro autorisé, AudioContext=${audioContext.state}`) const analyser = audioContext.createAnalyser() analyser.fftSize = 2048 analyser.smoothingTimeConstant = 0.85 @@ -482,15 +508,18 @@ export default function App() { mediaStreamRef.current = stream setIsAutoListening(true) setVoiceStatus('Micro actif. Parle quand tu veux, j’enverrai après 2,5 s de silence.') + pushAudioDebug(`Piste micro active=${stream.getAudioTracks()[0]?.readyState || 'inconnue'}`) await startSegmentRecording() monitorMicrophone() } catch { + pushAudioDebug('Accès micro refusé ou indisponible') setVoiceStatus('Accès au micro refusé ou indisponible.') stopMediaStream() } } function deactivateAutoListening(resetStatus = true) { + pushAudioDebug('Désactivation micro auto') setIsAutoListening(false) if (mediaRecorderRef.current && mediaRecorderRef.current.state !== 'inactive') { mediaRecorderRef.current.onstop = null @@ -634,6 +663,11 @@ export default function App() { )} {voiceStatus &&

{voiceStatus}

} {errorMessage &&

{errorMessage}

} + {DEBUG_AUDIO && ( +
+ {audioDebug.length === 0 ? 'Debug audio: aucune trace' : `Debug audio: ${audioDebug.join(' | ')}`} +
+ )} )