Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 160x 160x 160x 160x 160x 160x 160x 160x 160x 160x 160x 160x 160x 160x 160x 160x 160x 160x 160x 160x 160x 160x 160x 160x 160x 160x | 'use client'
import { useEffect, useRef } from 'react'
import { useTranslations } from 'next-intl'
import { useAudioManager } from '@/hooks/useAudioManager'
import { useTTS } from '@/hooks/useTTS'
import type { PedagogicalSegment } from '../DecompositionWithReasons'
import { useTutorialUI } from '../TutorialUIContext'
export function CoachBar() {
const ui = useTutorialUI()
const t = useTranslations('tutorial.coachBar')
const { isEnabled: audioHelpEnabled } = useAudioManager()
const seg: PedagogicalSegment | null = ui.activeSegment
const lastSummaryRef = useRef<string>('')
// Read coach hint aloud via TTS voice chain when it changes
// Uses hash-based clip ID so each unique summary gets its own mp3
const summary = seg?.readable?.summary ?? ''
const sayCoachHint = useTTS(summary ? { say: { en: summary }, tone: 'tutorial-instruction' } : '')
useEffect(() => {
if (!audioHelpEnabled || !summary || summary === lastSummaryRef.current) return
lastSummaryRef.current = summary
sayCoachHint()
}, [audioHelpEnabled, summary, sayCoachHint])
if (!ui.showCoachBar || !seg || !seg.readable?.summary) return null
const r = seg.readable
return (
<aside className="coachbar" role="status" aria-live="polite" data-test-id="coachbar">
<div className="coachbar__row">
<div className="coachbar__title">{r.title ?? t('titleFallback')}</div>
{ui.canHideCoachBar && (
<button
type="button"
className="coachbar__hide"
onClick={() => ui.setShowCoachBar(false)}
aria-label={t('hideAria')}
>
✕
</button>
)}
</div>
<p className="coachbar__summary">{r.summary}</p>
{(r.chips?.length ?? 0) > 0 && (
<div className="coachbar__chips">
{r.chips.slice(0, 2).map((c, i) => (
<span key={i} className="coachbar__chip">
{c.label}: {c.value}
</span>
))}
</div>
)}
</aside>
)
}
|