From cb3d977d10a5f250d8db21fffe7da33c8abaff6e Mon Sep 17 00:00:00 2001 From: Tanuja Pansare Date: Mon, 19 Oct 2026 14:21:33 +0530 Subject: [PATCH] Revert unintended avatar asset changes --- public/embed-avatar.html | 19 +- public/embed/avatar-page.css | 33 +- public/embed/avatar-page.js | 617 +++++++++------------------- public/embed/avatar/avatar3d.js | 646 ------------------------------ public/embed/avatar/model.glb | Bin 5472488 -> 0 bytes public/embed/simli-client.iife.js | 98 ----- 6 files changed, 204 insertions(+), 1209 deletions(-) delete mode 100644 public/embed/avatar/avatar3d.js delete mode 100644 public/embed/avatar/model.glb delete mode 100644 public/embed/simli-client.iife.js diff --git a/public/embed-avatar.html b/public/embed-avatar.html index 5ffaeaa..fe024ae 100644 --- a/public/embed-avatar.html +++ b/public/embed-avatar.html @@ -9,11 +9,10 @@
-
- - - -
Starting…
+ + + +
Starting…
@@ -43,14 +42,6 @@ }; - - - + diff --git a/public/embed/avatar-page.css b/public/embed/avatar-page.css index 4a01b41..afc6e31 100644 --- a/public/embed/avatar-page.css +++ b/public/embed/avatar-page.css @@ -29,32 +29,17 @@ body { } .eba-avatar { - position: relative; - background: #020617; - min-width: 0; - min-height: 0; - overflow: hidden; + position: relative; + background: #020617; + min-width: 0; + min-height: 0; } -#eba-avatar-stage { - position: absolute; - inset: 0; - width: 100%; - height: 100%; -} - -#eba-avatar-stage canvas { - display: block; - width: 100%; - height: 100%; -} - -#eba-audio { - position: absolute; - width: 1px; - height: 1px; - opacity: 0; - pointer-events: none; +#eba-video { + width: 100%; + height: 100%; + object-fit: cover; + background: #020617; } #eba-audio { diff --git a/public/embed/avatar-page.js b/public/embed/avatar-page.js index b021ef7..ad85fa1 100644 --- a/public/embed/avatar-page.js +++ b/public/embed/avatar-page.js @@ -4,10 +4,8 @@ const apiBase = String(cfg.apiBase || '').replace(/\/$/, ''); const Livekit = window.LivekitClient || window.livekit; const videoEl = document.getElementById('eba-video'); -// const audioEl = document.getElementById('eba-audio'); -// const waveEl = document.getElementById('eba-wave'); const audioEl = document.getElementById('eba-audio'); -const avatarStageEl = document.getElementById('eba-avatar-stage'); +const waveEl = document.getElementById('eba-wave'); const messagesEl = document.getElementById('eba-messages'); const errorEl = document.getElementById('eba-error'); const formEl = document.getElementById('eba-form'); @@ -22,10 +20,10 @@ const subEl = document.getElementById('eba-sub'); let sessionId = null; let busy = false; let livekitRoom = null; -// let simliClient = null; +let simliClient = null; let started = false; let chatReady = false; -// let pipedTrackId = null; +let pipedTrackId = null; let seenTranscriptIds = {}; let chipsEl = null; const mutedLivekitAudioEls = []; @@ -34,24 +32,22 @@ let pendingQuickReplies = []; let greetingShown = false; let avatarReadySent = false; let lastAgentMediaTrack = null; -// let lastAgentTrackClone = null; +let lastAgentTrackClone = null; let sessionAlive = false; -// let simliRestarting = false; -// let simliIdle = false; -// let simliGeneration = 0; +let simliRestarting = false; +let simliIdle = false; +let simliGeneration = 0; let videoAvatarOn = false; let waveAudioCtx = null; let userAnalyser = null; let agentAnalyser = null; -let avatarAudioSource = null; -let avatarAudioAnalyser = null; let waveRaf = 0; -// let simliInputEnabled = false; +let simliInputEnabled = false; let agentPipelineState = ''; let micListening = true; let micPublished = false; let micResumeTimer = 0; -// const SIMLI_AUDIO_BUFFER = 3000; +const SIMLI_AUDIO_BUFFER = 3000; const AGENT_STATE_ATTR = 'lk.agent.state'; function setStatus(text) { @@ -263,10 +259,6 @@ async function signalAvatarReady(room) { try { if (typeof room.localParticipant.publishData === 'function') { await room.localParticipant.publishData(payload, { reliable: true }); - console.log('[DEBUG] avatar-ready SENT', { - identity: room.localParticipant.identity, - room: room.name, - }); } } catch (err) { console.warn('[avatar] ready signal failed', err); @@ -314,7 +306,7 @@ async function sendMessage() { inputEl.value = ''; renderChips([]); addMessage('user', text); - // ensureSimliConnected('chat'); + ensureSimliConnected('chat'); setBusy(true); try { const data = await api(`/public/concierge/${encodeURIComponent(botId)}/chat`, { @@ -453,28 +445,28 @@ async function setMicListening(on) { function applyAgentState(state) { const next = String(state || '').toLowerCase(); if (!next) return; - agentPipelineState = next; - const speaking = next === 'speaking'; const holdMic = speaking || next === 'thinking'; + if (videoAvatarOn) { + if (speaking && lastAgentMediaTrack) { + if (!simliInputEnabled) connectSimliTts(lastAgentMediaTrack); + } else if (!speaking) { + disconnectSimliTts(); + } + } + if (holdMic) { setMicListening(false); } else { - micResumeTimer = window.setTimeout( - () => setMicListening(true), - 700 - ); + micResumeTimer = window.setTimeout(() => setMicListening(true), 700); } - if (next === 'thinking') { - setStatus('Thinking'); - } else if (speaking) { - setStatus('Speaking'); - } else { - setStatus('Listening'); - } + if (simliIdle || simliRestarting) return; + if (next === 'thinking') setStatus('Thinking'); + else if (speaking) setStatus('Speaking'); + else setStatus('Listening'); } function makeSimliClient(SimliClient, LogLevel, token) { @@ -552,106 +544,24 @@ function ensureWaveAudioCtx() { if (waveAudioCtx.state === 'suspended') waveAudioCtx.resume().catch(() => {}); return waveAudioCtx; } -function hookAvatarAnalyser(mediaTrack) { - if (!mediaTrack) { - return; - } - - const ctx = ensureWaveAudioCtx(); - - if (!ctx) { - console.warn( - '[avatar3d] AudioContext unavailable' - ); - return; - } - - try { - if (avatarAudioSource) { - try { - avatarAudioSource.disconnect(); - } catch { - // ignore - } - - avatarAudioSource = null; - } - - const clone = - typeof mediaTrack.clone === 'function' - ? mediaTrack.clone() - : mediaTrack; - - const stream = - new MediaStream([clone]); - - avatarAudioSource = - ctx.createMediaStreamSource( - stream - ); - - avatarAudioAnalyser = - ctx.createAnalyser(); - - avatarAudioAnalyser.fftSize = 256; - - avatarAudioAnalyser.smoothingTimeConstant = 0.72; - - avatarAudioSource.connect( - avatarAudioAnalyser - ); - - if ( - window.EBAvatar3D && - typeof window.EBAvatar3D.setAudioAnalyser === - 'function' - ) { - window.EBAvatar3D.setAudioAnalyser( - avatarAudioAnalyser - ); - } - - console.info( - '[avatar3d] agent audio connected to avatar' - ); - } catch (err) { - console.warn( - '[avatar3d] audio analyser failed', - err - ); - } -} function hookWaveAnalyser(mediaTrack, slot) { if (!mediaTrack || videoAvatarOn) return; - const ctx = ensureWaveAudioCtx(); if (!ctx) return; - try { - const clone = - typeof mediaTrack.clone === 'function' - ? mediaTrack.clone() - : mediaTrack; - - const source = ctx.createMediaStreamSource( - new MediaStream([clone]) - ); - + const clone = typeof mediaTrack.clone === 'function' ? mediaTrack.clone() : mediaTrack; + const source = ctx.createMediaStreamSource(new MediaStream([clone])); const analyser = ctx.createAnalyser(); analyser.fftSize = 256; analyser.smoothingTimeConstant = 0.72; - source.connect(analyser); - - if (slot === 'user') { - userAnalyser = analyser; - } else { - agentAnalyser = analyser; - } + if (slot === 'user') userAnalyser = analyser; + else agentAnalyser = analyser; } catch (err) { console.warn('[avatar] wave analyser failed', err); } + startWaveLoop(); } function analyserSnapshot(analyser) { @@ -746,181 +656,150 @@ function applyStageMode() { } function handleIncomingAudio(track, participant, room) { - console.log('[DEBUG] handleIncomingAudio()', { - kind: track?.kind, - participant: participant?.identity, - isLocal: isLocalParticipant(room, participant) - }); - if ( - !track || - String(track.kind || '').toLowerCase() !== 'audio' - ) { - return; - } - - if (isLocalParticipant(room, participant)) { - return; - } - - // -------------------------------------------- - // Authoritative audio playback - // -------------------------------------------- - console.log('[DEBUG] Attaching agent audio'); - attachRemoteAudio(track, true); - - // -------------------------------------------- - // Same audio -> 3D avatar - // -------------------------------------------- - - const media = mediaTrackFrom(track); - console.log('[DEBUG] handleIncomingAudio()', { - kind: track?.kind, - participant: participant?.identity, - isLocal: isLocalParticipant(room, participant) - }); - - lastAgentMediaTrack = media; - - if (media) { - hookAvatarAnalyser(media); - } - - setTimeout(() => { - signalAvatarReady(room); - }, 400); + if (!track || String(track.kind || '').toLowerCase() !== 'audio') return; + if (isLocalParticipant(room, participant)) return; + attachRemoteAudio(track, true); + const media = mediaTrackFrom(track); + lastAgentMediaTrack = media; + if (videoAvatarOn) { + if (agentPipelineState === 'speaking') connectSimliTts(media); + } else { + hookWaveAnalyser(media, 'agent'); + setTimeout(() => signalAvatarReady(room), 400); + } } -// function ensureSimliConnected(reason) { -// if (!videoAvatarOn) return; -// if (!sessionAlive || !livekitRoom || simliRestarting) return; -// if (!simliIdle && simliClient) return; -// restartSimliAvatar(reason || 'session-restart'); -// } +function ensureSimliConnected(reason) { + if (!videoAvatarOn) return; + if (!sessionAlive || !livekitRoom || simliRestarting) return; + if (!simliIdle && simliClient) return; + restartSimliAvatar(reason || 'session-restart'); +} -// async function restartSimliAvatar(reason) { -// if (!sessionAlive || simliRestarting || !livekitRoom) return; -// simliRestarting = true; -// // simliGeneration += 1; -// setStatus('Reconnecting avatar…'); -// console.warn('[avatar] restarting simli', reason); -// try { -// const prev = simliClient; -// simliClient = null; -// if (prev && typeof prev.stop === 'function') { -// await prev.stop().catch(() => {}); -// } -// const simli = await api(`/public/concierge/${encodeURIComponent(botId)}/simli/token`, { -// method: 'POST', -// body: {}, -// }); -// if (!simli?.sessionToken) throw new Error('Avatar session token was empty'); -// const { SimliClient, LogLevel } = await loadSimliClient(); -// const next = makeSimliClient(SimliClient, LogLevel, simli.sessionToken); -// bindSimliEvents(next); -// simliClient = next; -// await next.start(); -// const played = await resumeSimliAudio(); -// pipedTrackId = null; -// if (played) muteLivekitPlayback(); -// else unmuteLivekitFallback(); -// // simliIdle = false; -// applyAgentState(readRemoteAgentState(livekitRoom) || agentPipelineState); -// } catch (err) { -// console.warn('[avatar] simli restart failed', err); -// simliIdle = true; -// setStatus('Avatar disconnected'); -// } finally { -// simliRestarting = false; -// } -// } +async function restartSimliAvatar(reason) { + if (!sessionAlive || simliRestarting || !livekitRoom) return; + simliRestarting = true; + simliGeneration += 1; + setStatus('Reconnecting avatar…'); + console.warn('[avatar] restarting simli', reason); + try { + const prev = simliClient; + simliClient = null; + if (prev && typeof prev.stop === 'function') { + await prev.stop().catch(() => {}); + } + const simli = await api(`/public/concierge/${encodeURIComponent(botId)}/simli/token`, { + method: 'POST', + body: {}, + }); + if (!simli?.sessionToken) throw new Error('Avatar session token was empty'); + const { SimliClient, LogLevel } = await loadSimliClient(); + const next = makeSimliClient(SimliClient, LogLevel, simli.sessionToken); + bindSimliEvents(next); + simliClient = next; + await next.start(); + const played = await resumeSimliAudio(); + pipedTrackId = null; + if (played) muteLivekitPlayback(); + else unmuteLivekitFallback(); + simliIdle = false; + applyAgentState(readRemoteAgentState(livekitRoom) || agentPipelineState); + } catch (err) { + console.warn('[avatar] simli restart failed', err); + simliIdle = true; + setStatus('Avatar disconnected'); + } finally { + simliRestarting = false; + } +} -// function bindSimliEvents(client) { -// if (!client || typeof client.on !== 'function') return; -// const gen = simliGeneration; -// let ready = false; -// client.on('start', () => { -// if (gen !== simliGeneration) return; -// ready = true; -// // simliIdle = false; -// muteLivekitPlayback(); -// unmuteSimliPlayback(); -// if (audioEl?.play) audioEl.play().catch(() => {}); -// applyAgentState(readRemoteAgentState(livekitRoom) || agentPipelineState); -// }); -// client.on('stop', () => { -// if (gen !== simliGeneration) return; -// simliIdle = true; -// setStatus('Idle — speak to continue'); -// }); -// client.on('error', (message) => { -// if (gen !== simliGeneration) return; -// console.warn('[avatar] simli error', message); -// if (sessionAlive && ready) restartSimliAvatar(message || 'error'); -// }); -// } +function bindSimliEvents(client) { + if (!client || typeof client.on !== 'function') return; + const gen = simliGeneration; + let ready = false; + client.on('start', () => { + if (gen !== simliGeneration) return; + ready = true; + simliIdle = false; + muteLivekitPlayback(); + unmuteSimliPlayback(); + if (audioEl?.play) audioEl.play().catch(() => {}); + applyAgentState(readRemoteAgentState(livekitRoom) || agentPipelineState); + }); + client.on('stop', () => { + if (gen !== simliGeneration) return; + simliIdle = true; + setStatus('Idle — speak to continue'); + }); + client.on('error', (message) => { + if (gen !== simliGeneration) return; + console.warn('[avatar] simli error', message); + if (sessionAlive && ready) restartSimliAvatar(message || 'error'); + }); +} -// async function loadSimliClient() { -// const mod = await import('/embed/simli-client.js'); -// const SimliClient = mod.SimliClient || mod.default?.SimliClient || mod.default; -// if (typeof SimliClient !== 'function') { -// throw new Error('SIMLI client failed to load'); -// } -// return { SimliClient, LogLevel: mod.LogLevel || mod.default?.LogLevel }; -// } +async function loadSimliClient() { + const mod = await import('/embed/simli-client.js'); + const SimliClient = mod.SimliClient || mod.default?.SimliClient || mod.default; + if (typeof SimliClient !== 'function') { + throw new Error('SIMLI client failed to load'); + } + return { SimliClient, LogLevel: mod.LogLevel || mod.default?.LogLevel }; +} -// async function resumeSimliAudio() { -// const ctx = simliClient?.audioContext; -// if (ctx && ctx.state === 'suspended' && typeof ctx.resume === 'function') { -// await ctx.resume().catch(() => {}); -// } -// unmuteSimliPlayback(); -// let played = false; -// if (audioEl?.play) { -// played = await audioEl.play().then(() => true).catch(() => false); -// } -// if (videoEl?.play) videoEl.play().catch(() => {}); -// return played; -// } +async function resumeSimliAudio() { + const ctx = simliClient?.audioContext; + if (ctx && ctx.state === 'suspended' && typeof ctx.resume === 'function') { + await ctx.resume().catch(() => {}); + } + unmuteSimliPlayback(); + let played = false; + if (audioEl?.play) { + played = await audioEl.play().then(() => true).catch(() => false); + } + if (videoEl?.play) videoEl.play().catch(() => {}); + return played; +} -// async function startSimliOverlay() { -// if (!videoAvatarOn || simliRestarting) return; -// simliRestarting = true; -// try { -// setStatus('Connecting avatar…'); -// const simli = await api(`/public/concierge/${encodeURIComponent(botId)}/simli/token`, { -// method: 'POST', -// body: {}, -// }); -// if (!simli?.sessionToken) throw new Error('Avatar session token was empty'); -// const { SimliClient, LogLevel } = await loadSimliClient(); -// const next = makeSimliClient(SimliClient, LogLevel, simli.sessionToken); -// bindSimliEvents(next); -// simliClient = next; -// await next.start(); -// const played = await resumeSimliAudio(); -// pipedTrackId = null; -// if (played) muteLivekitPlayback(); -// else unmuteLivekitFallback(); -// applyAgentState(readRemoteAgentState(livekitRoom) || agentPipelineState); -// } catch (err) { -// console.warn('[avatar] simli overlay failed, voice continues', err); -// simliClient = null; -// unmuteLivekitFallback(); -// setStatus('Voice only'); -// } finally { -// simliRestarting = false; -// } -// } +async function startSimliOverlay() { + if (!videoAvatarOn || simliRestarting) return; + simliRestarting = true; + try { + setStatus('Connecting avatar…'); + const simli = await api(`/public/concierge/${encodeURIComponent(botId)}/simli/token`, { + method: 'POST', + body: {}, + }); + if (!simli?.sessionToken) throw new Error('Avatar session token was empty'); + const { SimliClient, LogLevel } = await loadSimliClient(); + const next = makeSimliClient(SimliClient, LogLevel, simli.sessionToken); + bindSimliEvents(next); + simliClient = next; + await next.start(); + const played = await resumeSimliAudio(); + pipedTrackId = null; + if (played) muteLivekitPlayback(); + else unmuteLivekitFallback(); + applyAgentState(readRemoteAgentState(livekitRoom) || agentPipelineState); + } catch (err) { + console.warn('[avatar] simli overlay failed, voice continues', err); + simliClient = null; + unmuteLivekitFallback(); + setStatus('Voice only'); + } finally { + simliRestarting = false; + } +} async function teardownMedia() { sessionAlive = false; - // simliIdle = false; - // simliGeneration += 1; + simliIdle = false; + simliGeneration += 1; if (micResumeTimer) { clearTimeout(micResumeTimer); micResumeTimer = 0; } - // disconnectSimliTts(); + disconnectSimliTts(); stopWaveLoop(); userAnalyser = null; agentAnalyser = null; @@ -933,8 +812,8 @@ async function teardownMedia() { micPublished = false; micListening = true; lastAgentMediaTrack = null; - // stopAgentTrackClone(); - // pipedTrackId = null; + stopAgentTrackClone(); + pipedTrackId = null; mutedLivekitAudioEls.splice(0).forEach((node) => { try { if (node.srcObject) node.srcObject = null; @@ -952,33 +831,15 @@ async function teardownMedia() { /* ignore */ } } - // const simli = simliClient; - // simliClient = null; - // if (simli && typeof simli.stop === 'function') { - // try { - // await simli.stop(); - // } catch { - // /* ignore */ - // } - // } - if (avatarAudioSource) { + const simli = simliClient; + simliClient = null; + if (simli && typeof simli.stop === 'function') { try { - avatarAudioSource.disconnect(); + await simli.stop(); } catch { - // ignore + /* ignore */ } - - avatarAudioSource = null; -} - -avatarAudioAnalyser = null; - -if ( - window.EBAvatar3D && - typeof window.EBAvatar3D.destroy === 'function' -) { - window.EBAvatar3D.destroy(); -} + } } async function maybeResume() { @@ -1018,34 +879,11 @@ async function loadChat() { if (!config.voiceEnabled) { throw new Error('Voice is not enabled for this chatbot'); } - videoAvatarOn = Boolean( - config.videoAvatarEnabled || - config.avatarEnabled -); - -if (videoAvatarOn) { - if ( - !window.EBAvatar3D || - typeof window.EBAvatar3D.init !== 'function' - ) { - throw new Error( - '3D avatar client failed to load' - ); - } - - await window.EBAvatar3D.init({ - container: avatarStageEl, - modelUrl: '/embed/avatar/model.glb', - }); - - console.info( - '[avatar3d] avatar initialized' - ); -} + videoAvatarOn = Boolean(config.videoAvatarEnabled || config.avatarEnabled); + applyStageMode(); } async function startSession() { - console.log('[DEBUG] startSession() START'); if (started) return; started = true; hideStartOverlay(); @@ -1060,11 +898,6 @@ async function startSession() { } await loadChat(); - console.log('[DEBUG] loadChat() DONE', { - videoAvatarOn, - avatarStageEl: !!avatarStageEl, - EBAvatar3D: !!window.EBAvatar3D -}); if (!navigator.mediaDevices?.getUserMedia) { throw new Error('This browser cannot access the microphone'); @@ -1079,7 +912,7 @@ async function startSession() { sessionId = granted.sessionId || sessionId; sessionAlive = true; - // if (!videoAvatarOn) startWaveLoop(); + if (!videoAvatarOn) startWaveLoop(); const room = new Livekit.Room({ adaptiveStream: false, @@ -1099,13 +932,7 @@ async function startSession() { setStatus('Disconnected'); } }); - room.on(Livekit.RoomEvent.TrackSubscribed, (track, pub, participant) => { - console.log('[DEBUG] TrackSubscribed', { - kind: track?.kind, - source: pub?.source, - participant: participant?.identity, - trackSid: pub?.trackSid - }); + room.on(Livekit.RoomEvent.TrackSubscribed, (track, _pub, participant) => { handleIncomingAudio(track, participant, room); }); if (Livekit.RoomEvent.LocalTrackPublished) { @@ -1132,9 +959,8 @@ async function startSession() { const local = isLocalParticipant(room, participant); (segments || []).forEach((seg) => { if (!seg || !seg.final) return; - if (agentPipelineState === 'speaking' && local) { - return; - } + if (agentPipelineState === 'speaking' && local) return; + ensureSimliConnected(local ? 'user-speech' : 'agent-speech'); if (rememberTranscript(seg.id || `${local ? 'user' : 'bot'}:${seg.text}`)) return; if (!local && pendingGreeting && normalizeSpeech(seg.text) === normalizeSpeech(pendingGreeting)) { showPendingGreeting(); @@ -1144,67 +970,17 @@ async function startSession() { }); }); room.on(Livekit.RoomEvent.ActiveSpeakersChanged, (speakers) => { - if (videoAvatarOn) return; - - const localId = room.localParticipant?.identity; - - const agentSpeaking = (speakers || []).some( - (p) => p?.identity && p.identity !== localId - ); - - setStatus(agentSpeaking ? 'Speaking' : 'Listening'); -}); - -room.on(Livekit.RoomEvent.ParticipantConnected, (participant) => { - console.log('[DEBUG] ParticipantConnected', { - identity: participant?.identity, - sid: participant?.sid, - }); - - participant.trackPublications.forEach((pub) => { - console.log('[DEBUG] Remote publication', { - kind: pub?.kind, - source: pub?.source, - trackSid: pub?.trackSid, - subscribed: pub?.isSubscribed, - hasTrack: !!pub?.track, - }); - - if (pub?.track) { - handleIncomingAudio(pub.track, participant, room); + if ((speakers || []).length) ensureSimliConnected('speakers'); + if (simliIdle || simliRestarting || agentPipelineState === 'speaking' || agentPipelineState === 'thinking') { + return; } + if (videoAvatarOn) return; + const localId = room.localParticipant?.identity; + const agentSpeaking = (speakers || []).some((p) => p?.identity && p.identity !== localId); + setStatus(agentSpeaking ? 'Speaking' : 'Listening'); }); -}); - - -room.on(Livekit.RoomEvent.ParticipantConnected, (participant) => { - console.log('[DEBUG] ParticipantConnected', { - identity: participant?.identity, - sid: participant?.sid, - }); -}); - -room.on(Livekit.RoomEvent.ParticipantDisconnected, (participant) => { - console.log('[DEBUG] ParticipantDisconnected', { - identity: participant?.identity, - sid: participant?.sid, - }); -}); - -room.on(Livekit.RoomEvent.LocalTrackPublished, (publication) => { - console.log('[DEBUG] LocalTrackPublished', { - kind: publication?.kind, - source: publication?.source, - trackSid: publication?.trackSid, - }); -}); - await room.connect(granted.url, granted.token); - console.log('[DEBUG] LiveKit CONNECTED', { - localParticipant: room.localParticipant?.identity, - remoteParticipants: room.remoteParticipants?.size - }); if (typeof room.startAudio === 'function') { await room.startAudio().catch(() => {}); } @@ -1228,11 +1004,6 @@ room.on(Livekit.RoomEvent.LocalTrackPublished, (publication) => { } if (!micPub) throw new Error('Microphone did not publish. Allow mic access and try again.'); micPublished = true; - console.log('[DEBUG] MIC PUBLISHED', { - trackSid: micPub?.trackSid, - kind: micPub?.kind, - source: micPub?.source, -}); micListening = true; const localMedia = mediaTrackFrom(micPub.track); if (localMedia) hookWaveAnalyser(localMedia, 'user'); @@ -1247,25 +1018,17 @@ room.on(Livekit.RoomEvent.LocalTrackPublished, (publication) => { setStatus('Listening'); return; } - // setStatus('Connecting avatar…'); - // try { - // await Promise.race([ - // startSimliOverlay(), - // new Promise((resolve) => setTimeout(resolve, 2500)), - // ]); - // } catch (err) { - // console.warn('[avatar] simli overlay', err); - // } - // await signalAvatarReady(room); - // applyAgentState(readRemoteAgentState(room) || agentPipelineState); - setStatus('Listening'); - -await signalAvatarReady(room); - -applyAgentState( - readRemoteAgentState(room) || - agentPipelineState -); + setStatus('Connecting avatar…'); + try { + await Promise.race([ + startSimliOverlay(), + new Promise((resolve) => setTimeout(resolve, 2500)), + ]); + } catch (err) { + console.warn('[avatar] simli overlay', err); + } + await signalAvatarReady(room); + applyAgentState(readRemoteAgentState(room) || agentPipelineState); } function needsGesture(err) { diff --git a/public/embed/avatar/avatar3d.js b/public/embed/avatar/avatar3d.js deleted file mode 100644 index 1307f7f..0000000 --- a/public/embed/avatar/avatar3d.js +++ /dev/null @@ -1,646 +0,0 @@ -(function () { - 'use strict'; - - let THREE = null; - let GLTFLoader = null; - - let scene = null; - let camera = null; - let renderer = null; - let model = null; - let container = null; - - let animationFrame = 0; - - let analyser = null; - let frequencyData = null; - - const morphs = { - jawOpen: [], - mouthClose: [], - sil: [], - }; - - let initialized = false; - - // -------------------------------------------------- - // Load Three.js + GLTFLoader - // -------------------------------------------------- - - async function loadThree() { - if (THREE && GLTFLoader) { - return; - } - - const threeModule = await import( - 'https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js' - ); - - THREE = threeModule; - - const loaderModule = await import( - 'https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/loaders/GLTFLoader.js' - ); - - GLTFLoader = loaderModule.GLTFLoader; - } - - // -------------------------------------------------- - // Initialize - // -------------------------------------------------- - - async function init(options = {}) { - if (initialized) { - return window.EBAvatar3D; - } - - container = - options.container || - document.getElementById('eba-avatar-stage'); - - if (!container) { - throw new Error( - '[avatar3d] #eba-avatar-stage not found' - ); - } - - await loadThree(); - - createRenderer(); - createScene(); - createCamera(); - createLights(); - - await loadModel( - options.modelUrl || '/embed/avatar/model.glb' - ); - - setupResize(); - - initialized = true; - - startRenderLoop(); - - console.info('[avatar3d] initialized'); - - return window.EBAvatar3D; - } - - // -------------------------------------------------- - // Renderer - // -------------------------------------------------- - - function createRenderer() { - renderer = new THREE.WebGLRenderer({ - antialias: true, - alpha: false, - powerPreference: 'high-performance', - }); - - renderer.setPixelRatio( - Math.min(window.devicePixelRatio || 1, 2) - ); - - renderer.setSize( - container.clientWidth || 640, - container.clientHeight || 640, - false - ); - - renderer.outputColorSpace = THREE.SRGBColorSpace; - - renderer.domElement.id = 'eba-avatar-canvas'; - - renderer.domElement.style.display = 'block'; - renderer.domElement.style.width = '100%'; - renderer.domElement.style.height = '100%'; - - container.appendChild(renderer.domElement); - } - - // -------------------------------------------------- - // Scene - // -------------------------------------------------- - - function createScene() { - scene = new THREE.Scene(); - - scene.background = new THREE.Color(0x020617); - } - - // -------------------------------------------------- - // Camera - // -------------------------------------------------- - - function createCamera() { - const width = - container.clientWidth || 640; - - const height = - container.clientHeight || 640; - - camera = new THREE.PerspectiveCamera( - 30, - width / height, - 0.01, - 100 - ); - - camera.position.set( - 0, - 1.55, - 2.7 - ); - - camera.lookAt( - 0, - 1.45, - 0 - ); - } - - // -------------------------------------------------- - // Lights - // -------------------------------------------------- - - function createLights() { - const ambient = - new THREE.HemisphereLight( - 0xffffff, - 0x223344, - 2 - ); - - scene.add(ambient); - - const key = - new THREE.DirectionalLight( - 0xffffff, - 2.5 - ); - - key.position.set(2, 3, 4); - - scene.add(key); - - const fill = - new THREE.DirectionalLight( - 0xaac8ff, - 1 - ); - - fill.position.set(-3, 2, 2); - - scene.add(fill); - } - - // -------------------------------------------------- - // Load GLB - // -------------------------------------------------- - - async function loadModel(url) { - return new Promise((resolve, reject) => { - const loader = new GLTFLoader(); - - console.info( - '[avatar3d] loading model:', - url - ); - - loader.load( - url, - - (gltf) => { - model = gltf.scene; - - scene.add(model); - - inspectModel(model); - - fitModel(); - - console.info( - '[avatar3d] model loaded' - ); - - resolve(model); - }, - - (progress) => { - if (progress.total) { - const percent = - (progress.loaded / - progress.total) * - 100; - - console.info( - `[avatar3d] loading ${percent.toFixed(0)}%` - ); - } - }, - - (error) => { - console.error( - '[avatar3d] model loading failed', - error - ); - - reject(error); - } - ); - }); - } - - // -------------------------------------------------- - // Find morph targets - // -------------------------------------------------- - - function inspectModel(root) { - Object.keys(morphs).forEach((key) => { - morphs[key].length = 0; - }); - - root.traverse((object) => { - if (!object.isMesh) { - return; - } - - if ( - !object.morphTargetDictionary || - !object.morphTargetInfluences - ) { - return; - } - - console.info( - '[avatar3d] morph mesh:', - object.name - ); - - registerMorph( - object, - 'jawOpen' - ); - - registerMorph( - object, - 'mouthClose' - ); - - registerMorph( - object, - 'sil' - ); - }); - - console.info( - '[avatar3d] jawOpen targets:', - morphs.jawOpen.length - ); - } - - function registerMorph(mesh, name) { - const dictionary = - mesh.morphTargetDictionary; - - if ( - dictionary && - Object.prototype.hasOwnProperty.call( - dictionary, - name - ) - ) { - morphs[name].push({ - mesh, - index: dictionary[name], - }); - } - } - - // -------------------------------------------------- - // Fit model - // -------------------------------------------------- - - function fitModel() { - if (!model) { - return; - } - - const box = - new THREE.Box3().setFromObject(model); - - const size = - new THREE.Vector3(); - - const center = - new THREE.Vector3(); - - box.getSize(size); - box.getCenter(center); - - console.info( - '[avatar3d] model size:', - size - ); - - model.position.x -= center.x; - model.position.y -= box.min.y; - model.position.z -= center.z; - - const maxDimension = - Math.max( - size.x, - size.y, - size.z - ); - - if (maxDimension > 5) { - const scale = - 2 / maxDimension; - - model.scale.setScalar(scale); - } - } - - // -------------------------------------------------- - // Audio analyser - // -------------------------------------------------- - - function setAudioAnalyser(nextAnalyser) { - analyser = nextAnalyser; - - if (analyser) { - frequencyData = - new Uint8Array( - analyser.frequencyBinCount - ); - - console.info( - '[avatar3d] audio analyser connected' - ); - } else { - frequencyData = null; - - console.info( - '[avatar3d] audio analyser disconnected' - ); - } - } - - function getAudioLevel() { - if (!analyser || !frequencyData) { - return 0; - } - - analyser.getByteFrequencyData( - frequencyData - ); - - let sum = 0; - - for ( - let i = 0; - i < frequencyData.length; - i++ - ) { - const value = - frequencyData[i] / 255; - - sum += value * value; - } - - const rms = - Math.sqrt( - sum / - frequencyData.length - ); - - return Math.min( - 1, - rms * 2.5 - ); - } - - // -------------------------------------------------- - // Lip movement - // -------------------------------------------------- - - function updateMouth() { - const level = - getAudioLevel(); - - const target = - Math.min( - 1, - Math.max( - 0, - level * 2.2 - ) - ); - - // Jaw opens when agent speaks. - morphs.jawOpen.forEach( - (targetInfo) => { - const current = - targetInfo.mesh - .morphTargetInfluences[ - targetInfo.index - ] || 0; - - targetInfo.mesh - .morphTargetInfluences[ - targetInfo.index - ] = - THREE.MathUtils.lerp( - current, - target, - 0.35 - ); - } - ); - - // Mouth close when silent. - morphs.mouthClose.forEach( - (targetInfo) => { - const current = - targetInfo.mesh - .morphTargetInfluences[ - targetInfo.index - ] || 0; - - targetInfo.mesh - .morphTargetInfluences[ - targetInfo.index - ] = - THREE.MathUtils.lerp( - current, - Math.max( - 0, - 1 - target - ), - 0.15 - ); - } - ); - - // Silence target. - morphs.sil.forEach( - (targetInfo) => { - const current = - targetInfo.mesh - .morphTargetInfluences[ - targetInfo.index - ] || 0; - - const silence = - target < 0.03 - ? 1 - : 0; - - targetInfo.mesh - .morphTargetInfluences[ - targetInfo.index - ] = - THREE.MathUtils.lerp( - current, - silence, - 0.15 - ); - } - ); - } - - // -------------------------------------------------- - // Render loop - // -------------------------------------------------- - - function startRenderLoop() { - if (animationFrame) { - return; - } - - const render = () => { - animationFrame = - requestAnimationFrame( - render - ); - - updateMouth(); - - if ( - renderer && - scene && - camera - ) { - renderer.render( - scene, - camera - ); - } - }; - - render(); - } - - // -------------------------------------------------- - // Resize - // -------------------------------------------------- - - function setupResize() { - window.addEventListener( - 'resize', - resize, - { passive: true } - ); - - resize(); - } - - function resize() { - if ( - !renderer || - !camera || - !container - ) { - return; - } - - const width = - container.clientWidth || 640; - - const height = - container.clientHeight || 640; - - camera.aspect = - width / height; - - camera.updateProjectionMatrix(); - - renderer.setSize( - width, - height, - false - ); - } - - // -------------------------------------------------- - // Destroy - // -------------------------------------------------- - - function destroy() { - if (animationFrame) { - cancelAnimationFrame( - animationFrame - ); - - animationFrame = 0; - } - - if (renderer) { - renderer.dispose(); - - if ( - renderer.domElement && - renderer.domElement.parentNode - ) { - renderer.domElement.parentNode.removeChild( - renderer.domElement - ); - } - } - - scene = null; - camera = null; - renderer = null; - model = null; - - analyser = null; - frequencyData = null; - - Object.keys(morphs).forEach( - (key) => { - morphs[key].length = 0; - } - ); - - initialized = false; - } - - // -------------------------------------------------- - // Public API - // -------------------------------------------------- - - window.EBAvatar3D = { - init, - setAudioAnalyser, - destroy, - - get initialized() { - return initialized; - }, - - get model() { - return model; - }, - }; -})(); \ No newline at end of file diff --git a/public/embed/avatar/model.glb b/public/embed/avatar/model.glb deleted file mode 100644 index 49a988be3187454d4ac4a26fa0af5c1d39b539af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5472488 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

(RuxjUs;H9&& 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

xJ~S7Q{nTWEb4b78{?!RGb{^ zPi?t8_{qYlHfir&pe>dZ?AT&2*@}Pag8pZ$-D^a@se(M}b1Z^a!orofRS$3XRn#RG zGWO-^0vYKw+^k)+$R(*UnRr)M>b4inwq>xTX7<&LGcg~W#@!5_Kcd|y1k}Cko+U?? zg%T5IgNvrn5ShD{v!Zkc4QvMi)R3||kF>xqc$W0;jb8)b;9F-1^dMW~<4YTR~?8Ky5+{SRHu% zH6hBkQxP-Bf%|R;gsczmyZm#OI_A|^c29NFbTjs<9Idn4c(_I*n(RAK>ZKoxY}9im ze^Vq+`*w_S?}EC`Qx@I6vXbD+49qQq3xoP-F!}9;L6T_~T!S=np^vaK-FCXS59K-b zRSNfa6uFKG@j;FL+dL>b`lqCV0Xh7!?~hs#?Ws{o&1gVplAq zgG$@R3ODU90?v>KRaEm9OTBtQ#VGK_t zRn^w|2Yk)vT}QzL-iG1X{kI{vDy7p%fuFuoIpwP*!$8f>D?28xnSWC5=aGQ46Ph3+ zl{ihNHO%vM9+3DSoWU(WP+bF1>6($D+> z8GjF}TCeKWJ~d(l{THTO)fl_EOLxE*$f2#LwLJU&WIfV+Qp@63KZDJ?Sn_P4tH1c| zPkZ>oF?qlY>r@32)T!=SX@Is#i#n}A(zA~Aoq5flSh}<~vmaXT*>Bw39NpK@5>r`Q za3Uh9;lTO_cW#-kN+G?q%iD zUC?GB2|>y|koAz z?AhBO1*TiaNCD?WcJLoR%nSthiatrU8P%izns1?Q5W?l~rSDSHMs%_LFG7xs5qI-8 zZdj)AEQU#9(IVi=9b{=EM(-b~V3b>xB)TgMj`sM*Fktpw1xW5pq%CYv%Myp=`%3{- zy2gYg5NDZ1ENQ^O9y~fz&>6&5)hELq9Ic?HDd+Upth(JNMLB&M#X9X%6D6!b!&t7?U6v2yz&m>COOn|PkA`2^TW(~VP*KOh<=fMXp4`Th!>qpwE zhe(q2X=fdeoth!^knUom&Im|$g|&mjEgl*;hbnFs{KAv6LDt2SJl;fFK?{0NJu`GV zyU{blKibU1%*|VxTiMo-qjTm!Jn=?@b0hkxI{TN%CZ`)W?gV`zX+Y{O35GtlMA#Y7 zpSTT9tL2H3+3E%zKzt$p!N=ol%ayZ%LL z90R>umpcEh{eTrpJF#k!3Ir`i8rv3Ymk{KM~W{0A`G@|fz+io56^xR@T-=746HlSKRq96zH!pd9qP$ zV@4w) z>8nQ+r14TbC*ftiQSdrYMHGt)&%#t=%ZwNxX%UO7^}j(!YPh97XQTQQJyNhTfa|qW z2OlsYTRf#M7qLJZV@Z6be$D6fnzBhxF;a3h3!+YZVN!_rlor#`w6qx+T$y!Vv36Cx z;|cX|5Lx;_lNbm}1W-bja(5FKnNqnw(as60`g}8*K+30FhVMHG*lEdRXg$IhY0=}MZAfBG07TqJvPu4K*@|yC0yv^KYxxK72}I-^xz9# zk(UeAv!8M|f~cwLxYo!D{mK&k=`g>B4Q2Q%d32dJXSCE)_^zm*Z7|9SIQr+8V1VJb zvv-G5g9QupDYiLJ>MCIPt{ZDGxo%8rQuiABiKKn-3#z9l0&0Z^m(-(o;(VIlPX{7& zJoWX!#zJc;V2nrXmv9*FU8gZj$~l4Fs!`sMRQ|WnA$dUG-oEUKK;5NjtUe`D%baCQ zvX#)Sz+_QmBEvnYW1#$g%IzlGF{8B(Km<`YtJ_UcgYe2=U0&|f!>Y&rJ|K;?1A