import { useEffect, useRef, useState } from "react"; interface AITutorAvatarProps { /** Whether the avatar is currently speaking. Drives the mouth animation. */ speaking?: boolean; /** Optional audio element whose playback drives `speaking` automatically. */ audio?: HTMLAudioElement | null; /** Pixel diameter. Defaults to 96px (good for sidebar). */ size?: number; /** Display name for the tooltip. */ name?: string; } /** * Lightweight CSS-only "talking head" avatar. We avoid a 3D engine on * purpose — most students open the platform on modest hardware and we * already ship a TTS pipeline. The avatar mouth animates while * `speaking` is true OR while the bound `audio` element is playing. * * Idle state: the head bobs gently and blinks every 4s. * Speaking state: the mouth opens/closes at ~5Hz with random amplitude. */ export default function AITutorAvatar({ speaking = false, audio, size = 96, name = "AI Tutor", }: AITutorAvatarProps) { const [audioActive, setAudioActive] = useState(false); const blinkRef = useRef(null); const [blink, setBlink] = useState(false); useEffect(() => { if (!audio) return; const onPlay = () => setAudioActive(true); const onStop = () => setAudioActive(false); audio.addEventListener("play", onPlay); audio.addEventListener("playing", onPlay); audio.addEventListener("pause", onStop); audio.addEventListener("ended", onStop); return () => { audio.removeEventListener("play", onPlay); audio.removeEventListener("playing", onPlay); audio.removeEventListener("pause", onStop); audio.removeEventListener("ended", onStop); }; }, [audio]); useEffect(() => { const tick = () => { setBlink(true); window.setTimeout(() => setBlink(false), 140); }; blinkRef.current = setInterval(tick, 3500 + Math.random() * 1500); return () => { if (blinkRef.current) clearInterval(blinkRef.current); }; }, []); const isSpeaking = speaking || audioActive; return (
{isSpeaking && ( speaking )}
); }