feat(t2-live): archi audio Voie A + Bugs 4/5/6 + indicateur de prise de parole (Sprint 6e)
- Voie A WAV : AudioContext unique au rate natif, tap AudioWorklet sur mixGain, uplink rate-aware 16k, alignement par horloge unique (fin offset/resample/concat). Anti-echo candidat. Cycle start=ws.onopen / stop=Terminer / cancel=aucun WAV. - Bug 4 : 'Voir le rapport' route vers le rapport (navigatingAwayRef). - Bug 5 : 'Annuler' (cancelDialogue) - arret sans evaluation, sans WAV, sans production. - Bug 6 : 'Nouvelle simulation' route selon le type via champ tache propage (Report). - Indicateur de prise de parole : state machine USER_SPEAKING/USER_SILENT (RMS + hysteresis). - Cleanup : retrait instrumentation [BISECT] ; ref VAD renomme lastAiChunkTsRef. - Removed : code mort mixTracksToInt16, resample16kTo24k + tests.
This commit is contained in:
parent
9bf95f5c05
commit
72795e924e
16 changed files with 848 additions and 257 deletions
|
|
@ -6,11 +6,12 @@
|
|||
* écran terminal avec deux boutons : "Télécharger l'audio" et "Voir le rapport".
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Mic, Download, FileText, Loader2 } from 'lucide-react'
|
||||
import { Button } from '@/shared/ui/Button'
|
||||
import { Card } from '@/shared/ui/Card'
|
||||
import { T2SpeakingIndicator } from '../components/T2SpeakingIndicator'
|
||||
import { useT2LiveContext } from '../state/T2LiveContext'
|
||||
import { useT2LiveSession } from '../hooks/useT2LiveSession'
|
||||
|
||||
|
|
@ -27,14 +28,20 @@ export function T2DialoguePage() {
|
|||
const navigate = useNavigate()
|
||||
const { sujet, reset: resetContext } = useT2LiveContext()
|
||||
const [autoStarted, setAutoStarted] = useState(false)
|
||||
// Bug 4 — neutralise le garde-fou `!sujet` lors d'une navigation volontaire
|
||||
// (Voir le rapport, Retour aux sujets) : sinon resetContext() déclenche la
|
||||
// redirection parasite vers /simulation/eo/t2 et écrase la navigation voulue.
|
||||
const navigatingAwayRef = useRef(false)
|
||||
|
||||
const session = useT2LiveSession({
|
||||
sujetId: sujet?.id ?? '',
|
||||
})
|
||||
|
||||
// Garde-fou : pas de sujet → retour à la sélection.
|
||||
// Garde-fou : pas de sujet → retour à la sélection (sauf navigation volontaire).
|
||||
useEffect(() => {
|
||||
if (!sujet) navigate('/simulation/eo/t2', { replace: true })
|
||||
if (!sujet && !navigatingAwayRef.current) {
|
||||
navigate('/simulation/eo/t2', { replace: true })
|
||||
}
|
||||
}, [sujet, navigate])
|
||||
|
||||
// Démarrer le dialogue automatiquement au mount (la prépa est déjà finie).
|
||||
|
|
@ -83,15 +90,34 @@ export function T2DialoguePage() {
|
|||
|
||||
function handleViewReport() {
|
||||
if (!session.simulationId) return
|
||||
// Bug 4 — neutralise le garde-fou avant resetContext() pour que la
|
||||
// navigation vers le rapport aboutisse. Le routage EO/EE du retour est
|
||||
// géré par RapportPage via `Report.tache` (Bug 6, voie B).
|
||||
navigatingAwayRef.current = true
|
||||
resetContext()
|
||||
navigate(`/rapport/${session.simulationId}`)
|
||||
}
|
||||
|
||||
function handleBackToSujets() {
|
||||
navigatingAwayRef.current = true
|
||||
resetContext()
|
||||
navigate('/simulation/eo/t2')
|
||||
}
|
||||
|
||||
// Bug 5 — Abandon : ferme la session sans évaluation (cancelDialogue ne
|
||||
// déclenche ni correction ni persistance), puis retour à la sélection T2.
|
||||
function handleCancel() {
|
||||
navigatingAwayRef.current = true
|
||||
session.cancelDialogue()
|
||||
resetContext()
|
||||
navigate('/simulation/eo/t2')
|
||||
}
|
||||
|
||||
// « Annuler » n'a de sens que pendant le dialogue actif (pas en connexion
|
||||
// ni en évaluation).
|
||||
const canCancel =
|
||||
session.state === 'ready' || session.state === 'speaking' || session.state === 'listening'
|
||||
|
||||
if (!sujet) return null
|
||||
|
||||
// ── État terminal : rapport prêt ─────────────────────────────────────────
|
||||
|
|
@ -194,10 +220,27 @@ export function T2DialoguePage() {
|
|||
)}
|
||||
<p className="text-sm font-semibold text-ink-primary">{stateLabel}</p>
|
||||
</div>
|
||||
{/* Indicateur de prise de parole. 'speaking' = amplitude micro réelle
|
||||
(analyser dérivé du graphe de capture, lu par ref en rAF) ;
|
||||
'listening' = animation décorative pilotée par l'état (pas de sonde
|
||||
playback) ; 'ready' = signal « À vous de parler ». */}
|
||||
{canCancel && (
|
||||
<T2SpeakingIndicator analyserRef={session.analyserRef} state={session.state} />
|
||||
)}
|
||||
<p className="text-xs text-ink-secondary">{sujet.consigne}</p>
|
||||
</Card>
|
||||
|
||||
<div className="flex justify-center">
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
{canCancel && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-danger hover:bg-danger-soft hover:text-danger"
|
||||
onClick={handleCancel}
|
||||
title="Abandonner sans générer de rapport"
|
||||
>
|
||||
Annuler
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => session.endDialogue()}
|
||||
|
|
@ -206,6 +249,9 @@ export function T2DialoguePage() {
|
|||
Terminer le dialogue
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-center text-xs text-ink-tertiary">
|
||||
« Annuler » abandonne la session sans rapport. « Terminer » lance l'évaluation.
|
||||
</p>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue