"use client"; import { LockKeyhole, Pause, Play } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { useCachedChatMediaUrl } from "@/lib/chat/use_cached_chat_media_url"; import styles from "./voice-bubble.module.css"; export interface VoiceBubbleProps { messageId?: string; audioUrl?: string | null; isFromAI: boolean; locked?: boolean; hint?: string | null; isUnlocking?: boolean; onUnlock?: () => void; } export function VoiceBubble({ messageId, audioUrl, isFromAI, locked = false, hint, isUnlocking = false, onUnlock, }: VoiceBubbleProps) { const audioRef = useRef(null); const [isPlaying, setIsPlaying] = useState(false); const [errorMediaUrl, setErrorMediaUrl] = useState(null); const hasAudio = audioUrl != null && audioUrl.length > 0; const { mediaUrl, isUsingCachedMedia, reportMediaError } = useCachedChatMediaUrl({ messageId, remoteUrl: audioUrl, kind: "audio", }); useEffect(() => { const audio = audioRef.current; if (!audio) return; const handleEnded = () => setIsPlaying(false); const handlePause = () => setIsPlaying(false); const handlePlay = () => setIsPlaying(true); const handleError = () => { if (isUsingCachedMedia) { reportMediaError(); return; } setErrorMediaUrl(mediaUrl); setIsPlaying(false); }; audio.addEventListener("ended", handleEnded); audio.addEventListener("pause", handlePause); audio.addEventListener("play", handlePlay); audio.addEventListener("error", handleError); return () => { audio.removeEventListener("ended", handleEnded); audio.removeEventListener("pause", handlePause); audio.removeEventListener("play", handlePlay); audio.removeEventListener("error", handleError); }; }, [isUsingCachedMedia, mediaUrl, reportMediaError]); const hasError = errorMediaUrl === mediaUrl; const handleToggle = () => { if (locked || !hasAudio) return; const audio = audioRef.current; if (!audio || hasError) return; if (isPlaying) { audio.pause(); return; } void audio.play().catch(() => { setErrorMediaUrl(mediaUrl); setIsPlaying(false); }); }; return (
{hasError ? (

Voice message failed to load.

) : null} {locked ? ( <>

{hint && hint.length > 0 ? hint : "Elio has a locked voice message for you."}

) : null}
{!locked && hasAudio ? (
); }