"use client"; import React, { useEffect, useMemo, useRef, useState } from "react" import { useAppSelector } from "@/common" import { TrulienceAvatar } from "trulience-sdk" import { IMicrophoneAudioTrack } from "agora-rtc-sdk-ng" import { Maximize, Minimize } from "lucide-react"; import { cn } from "@/lib/utils"; import { toast } from "sonner"; import { Progress, ProgressIndicator } from "../ui/progress"; interface AvatarProps { audioTrack?: IMicrophoneAudioTrack, localAudioTrack?: IMicrophoneAudioTrack } export default function Avatar({ audioTrack }: AvatarProps) { const agentConnected = useAppSelector((state) => state.global.agentConnected) const trulienceSettings = useAppSelector((state) => state.global.trulienceSettings) const trulienceAvatarRef = useRef(null) const [errorMessage, setErrorMessage] = useState("") // Track loading progress const [loadProgress, setLoadProgress] = useState(0) // State for the final avatar ID const [finalAvatarId, setFinalAvatarId] = useState("") // State for toggling fullscreen const [fullscreen, setFullscreen] = useState(false) // Safely read URL param on the client useEffect(() => { if (typeof window !== "undefined") { const urlParams = new URLSearchParams(window.location.search) const avatarIdFromURL = urlParams.get("avatarId") setFinalAvatarId( avatarIdFromURL || trulienceSettings.avatarId || "" ) } }, []) // Define event callbacks const eventCallbacks = useMemo(() => { return { "auth-success": (resp: string) => { console.log("Trulience Avatar auth-success:", resp) }, "auth-fail": (resp: any) => { console.log("Trulience Avatar auth-fail:", resp) setErrorMessage(resp.message) }, "websocket-connect": (resp: string) => { console.log("Trulience Avatar websocket-connect:", resp) }, "load-progress": (details: Record) => { console.log("Trulience Avatar load-progress:", details.progress) setLoadProgress(details.progress) }, } }, []) // Only create TrulienceAvatar instance once we have a final avatar ID const trulienceAvatarInstance = useMemo(() => { if (!finalAvatarId) return null return ( ) }, [finalAvatarId, eventCallbacks]) // Update the Avatar’s audio stream whenever audioTrack or agentConnected changes useEffect(() => { if (trulienceAvatarRef.current) { if (audioTrack && agentConnected) { const stream = new MediaStream([audioTrack.getMediaStreamTrack()]) trulienceAvatarRef.current.setMediaStream(null) trulienceAvatarRef.current.setMediaStream(stream) console.warn("[TrulienceAvatar] MediaStream set:", stream) } else if (!agentConnected) { const trulienceObj = trulienceAvatarRef.current.getTrulienceObject() trulienceObj?.sendMessageToAvatar("") trulienceObj?.sendMessageToAvatar("") } } // Cleanup: unset media stream return () => { trulienceAvatarRef.current?.setMediaStream(null) } }, [audioTrack, agentConnected]) return (
{/* Render the TrulienceAvatar */} {trulienceAvatarInstance} {/* Show a loader overlay while progress < 1 */} {errorMessage ? (
{errorMessage}
) : loadProgress < 1 && (
{/* Simple Tailwind spinner */}
)}
) }